Merge "Multiple shortcut menu for android R (1/n)"
diff --git a/Android.bp b/Android.bp
index 742a70e5..4b82e1d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -353,9 +353,6 @@
         "com.android.sysprop.apex",
         "PlatformProperties",
     ],
-    aidl: {
-        include_dirs: ["system/connectivity/wificond/aidl"],
-    },
     sdk_version: "core_platform",
     installable: false,
 }
@@ -605,7 +602,7 @@
 java_library {
     name: "framework-annotations-lib",
     srcs: [ ":framework-annotations" ],
-    sdk_version: "current",
+    sdk_version: "core_current",
 }
 
 filegroup {
@@ -699,7 +696,7 @@
         "core/proto/android/privacy.proto",
         "core/proto/android/section.proto",
     ],
-    sdk_version: "current",
+    sdk_version: "9",
     srcs: [
         "core/proto/**/*.proto",
         "libs/incident/proto/android/os/**/*.proto",
@@ -722,6 +719,7 @@
         "core/proto/android/privacy.proto",
         "core/proto/android/section.proto",
     ],
+    sdk_version: "core_current",
     // Protos have lots of MissingOverride and similar.
     errorprone: {
         javacflags: ["-XepDisableAllChecks"],
@@ -1024,645 +1022,6 @@
     ],
 }
 
-// Make the api/current.txt file available for use by modules in other
-// directories.
-filegroup {
-    name: "frameworks-base-api-current.txt",
-    srcs: [
-        "api/current.txt",
-    ],
-}
-
-// Make the api/system-current.txt file available for use by modules in other
-// directories.
-filegroup {
-    name: "frameworks-base-api-system-current.txt",
-    srcs: [
-        "api/system-current.txt",
-    ],
-}
-
-// Make the api/system-removed.txt file available for use by modules in other
-// directories.
-filegroup {
-    name: "frameworks-base-api-system-removed.txt",
-    srcs: [
-        "api/system-removed.txt",
-    ],
-}
-
-framework_docs_only_args = " -android -manifest $(location core/res/AndroidManifest.xml) " +
-    "-werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 " +
-    "-overview $(location core/java/overview.html) " +
-    // Federate Support Library references against local API file.
-    "-federate SupportLib https://developer.android.com " +
-    "-federationapi SupportLib $(location :current-support-api) " +
-    // Federate Support Library references against local API file.
-    "-federate AndroidX https://developer.android.com " +
-    "-federationapi AndroidX $(location :current-androidx-api) "
-
-framework_docs_only_libs = [
-    "voip-common",
-    "android.test.mock",
-    "android-support-annotations",
-    "android-support-compat",
-    "android-support-core-ui",
-    "android-support-core-utils",
-    "android-support-design",
-    "android-support-dynamic-animation",
-    "android-support-exifinterface",
-    "android-support-fragment",
-    "android-support-media-compat",
-    "android-support-percent",
-    "android-support-transition",
-    "android-support-v7-cardview",
-    "android-support-v7-gridlayout",
-    "android-support-v7-mediarouter",
-    "android-support-v7-palette",
-    "android-support-v7-preference",
-    "android-support-v13",
-    "android-support-v14-preference",
-    "android-support-v17-leanback",
-    "android-support-vectordrawable",
-    "android-support-animatedvectordrawable",
-    "android-support-v7-appcompat",
-    "android-support-v7-recyclerview",
-    "android-support-v8-renderscript",
-    "android-support-multidex",
-    "android-support-multidex-instrumentation",
-]
-
-metalava_framework_docs_args = "--manifest $(location core/res/AndroidManifest.xml) " +
-    "--ignore-classes-on-classpath " +
-    "--hide-package com.android.server " +
-    "--error UnhiddenSystemApi " +
-    "--hide RequiresPermission " +
-    "--hide CallbackInterface " +
-    "--hide MissingPermission --hide BroadcastBehavior " +
-    "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " +
-    "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo " +
-    "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.*"
-
-packages_to_document = [
-    "android",
-    "dalvik",
-    "java",
-    "javax",
-    "junit",
-    "org.apache.http",
-    "org.json",
-    "org.w3c.dom",
-    "org.xml.sax",
-    "org.xmlpull",
-]
-
-stubs_defaults {
-    name: "framework-doc-stubs-default",
-    srcs: [
-        ":framework-mime-sources",
-        ":framework-non-updatable-sources",
-        ":framework-updatable-sources",
-        "core/java/**/*.logtags",
-        "test-base/src/**/*.java",
-        ":opt-telephony-srcs",
-        ":opt-net-voip-srcs",
-        ":core-current-stubs-source",
-        ":core_public_api_files",
-        "test-mock/src/**/*.java",
-        "test-runner/src/**/*.java",
-    ],
-    libs: framework_docs_only_libs,
-    create_doc_stubs: true,
-    annotations_enabled: true,
-    api_levels_annotations_enabled: true,
-    api_levels_annotations_dirs: [
-        "sdk-dir",
-        "api-versions-jars-dir",
-    ],
-    previous_api: ":last-released-public-api",
-    merge_annotations_dirs: [
-        "metalava-manual",
-    ],
-}
-
-doc_defaults {
-    name: "framework-docs-default",
-    libs: framework_docs_only_libs +
-        ["stub-annotations"],
-    html_dirs: [
-        "docs/html",
-    ],
-    knowntags: [
-        "docs/knowntags.txt",
-        ":known-oj-tags",
-    ],
-    custom_template: "droiddoc-templates-sdk",
-    resourcesdir: "docs/html/reference/images/",
-    resourcesoutdir: "reference/android/images/",
-    hdf: [
-        "dac true",
-        "sdk.codename O",
-        "sdk.preview.version 1",
-        "sdk.version 7.0",
-        "sdk.rel.id 1",
-        "sdk.preview 0",
-    ],
-    arg_files: [
-        "core/res/AndroidManifest.xml",
-        "core/java/overview.html",
-        ":current-support-api",
-        ":current-androidx-api",
-    ],
-    create_stubs: false,
-}
-
-doc_defaults {
-    name: "framework-dokka-docs-default",
-    create_stubs: false,
-}
-
-stubs_defaults {
-    name: "metalava-api-stubs-default",
-    srcs: [
-        ":framework-non-updatable-sources",
-        ":framework-updatable-sources",
-        "core/java/**/*.logtags",
-        ":opt-telephony-srcs",
-        ":opt-net-voip-srcs",
-        ":core-current-stubs-source",
-        ":core_public_api_files",
-        ":ike-api-srcs",
-    ],
-    libs: ["framework-internal-utils"],
-    installable: false,
-    annotations_enabled: true,
-    previous_api: ":last-released-public-api",
-    merge_annotations_dirs: [
-        "metalava-manual",
-    ],
-    api_levels_annotations_enabled: true,
-    api_levels_annotations_dirs: [
-        "sdk-dir",
-        "api-versions-jars-dir",
-    ],
-    sdk_version: "core_platform",
-    filter_packages: packages_to_document,
-}
-
-droidstubs {
-    name: "framework-doc-stubs",
-    defaults: ["framework-doc-stubs-default"],
-    arg_files: [
-        "core/res/AndroidManifest.xml",
-    ],
-    args: metalava_framework_docs_args,
-    write_sdk_values: true,
-}
-
-droidstubs {
-    name: "framework-doc-system-stubs",
-    defaults: ["framework-doc-stubs-default"],
-    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\\) ",
-    write_sdk_values: true,
-}
-
-droiddoc {
-    name: "doc-comment-check-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
-    ],
-    args: framework_docs_only_args + " -referenceonly -parsecomments",
-    installable: false,
-}
-
-droiddoc {
-    name: "offline-sdk-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
-    ],
-    hdf: [
-        "android.whichdoc offline",
-    ],
-    proofread_file: "offline-sdk-docs-proofrerad.txt",
-    args: framework_docs_only_args + " -offlinemode -title \"Android SDK\"",
-    static_doc_index_redirect: "docs/docs-preview-index.html",
-}
-
-droiddoc {
-    // Please sync with android-api-council@ before making any changes for the name property below.
-    // Since there's cron jobs that fetch offline-sdk-referenceonly-docs-docs.zip periodically.
-    // See b/116221385 for reference.
-    name: "offline-sdk-referenceonly-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
-    ],
-    hdf: [
-        "android.whichdoc offline",
-    ],
-    proofread_file: "offline-sdk-referenceonly-docs-proofrerad.txt",
-    args: framework_docs_only_args + " -offlinemode -title \"Android SDK\" -referenceonly",
-    static_doc_index_redirect: "docs/docs-documentation-redirect.html",
-    static_doc_properties: "docs/source.properties",
-}
-
-droiddoc {
-    // Please sync with android-api-council@ before making any changes for the name property below.
-    // Since there's cron jobs that fetch offline-system-sdk-referenceonly-docs-docs.zip periodically.
-    // See b/116221385 for reference.
-    name: "offline-system-sdk-referenceonly-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-system-stubs",
-    ],
-    hdf: [
-        "android.whichdoc offline",
-    ],
-    proofread_file: "offline-system-sdk-referenceonly-docs-proofrerad.txt",
-    args: framework_docs_only_args + " -hide 101 -hide 104 -hide 108" +
-    " -offlinemode -title \"Android System SDK\" -referenceonly",
-    static_doc_index_redirect: "docs/docs-documentation-redirect.html",
-    static_doc_properties: "docs/source.properties",
-}
-
-droiddoc {
-    name: "online-sdk-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
-    ],
-    hdf: [
-        "android.whichdoc online",
-        "android.hasSamples true",
-    ],
-    proofread_file: "online-sdk-docs-proofrerad.txt",
-    args: framework_docs_only_args +
-        " -toroot / -samplegroup Admin " +
-        " -samplegroup Background " +
-        " -samplegroup Connectivity " +
-        " -samplegroup Content " +
-        " -samplegroup Input " +
-        " -samplegroup Media " +
-        " -samplegroup Notification " +
-        " -samplegroup RenderScript " +
-        " -samplegroup Security " +
-        " -samplegroup Sensors " +
-        " -samplegroup System " +
-        " -samplegroup Testing " +
-        " -samplegroup UI " +
-        " -samplegroup Views " +
-        " -samplegroup Wearable -samplesdir development/samples/browseable ",
-}
-
-droiddoc {
-    name: "online-system-api-sdk-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-system-stubs",
-    ],
-    hdf: [
-        "android.whichdoc online",
-        "android.hasSamples true",
-    ],
-    proofread_file: "online-system-api-sdk-docs-proofrerad.txt",
-    args: framework_docs_only_args +
-        " -referenceonly " +
-        " -title \"Android SDK - Including system APIs.\" " +
-        " -hide 101 " +
-        " -hide 104 " +
-        " -hide 108 " +
-        " -toroot / -samplegroup Admin " +
-        " -samplegroup Background " +
-        " -samplegroup Connectivity " +
-        " -samplegroup Content " +
-        " -samplegroup Input " +
-        " -samplegroup Media " +
-        " -samplegroup Notification " +
-        " -samplegroup RenderScript " +
-        " -samplegroup Security " +
-        " -samplegroup Sensors " +
-        " -samplegroup System " +
-        " -samplegroup Testing " +
-        " -samplegroup UI " +
-        " -samplegroup Views " +
-        " -samplegroup Wearable -samplesdir development/samples/browseable ",
-    installable: false,
-}
-
-droiddoc {
-    name: "ds-docs-java",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
-    ],
-    hdf: [
-        "android.whichdoc online",
-        "android.hasSamples true",
-    ],
-    proofread_file: "ds-docs-proofrerad.txt",
-    args: framework_docs_only_args +
-        " -toroot / -yamlV2 -metalavaApiSince -samplegroup Admin " +
-        " -samplegroup Background " +
-        " -samplegroup Connectivity " +
-        " -samplegroup Content " +
-        " -samplegroup Input " +
-        " -samplegroup Media " +
-        " -samplegroup Notification " +
-        " -samplegroup RenderScript " +
-        " -samplegroup Security " +
-        " -samplegroup Sensors " +
-        " -samplegroup System " +
-        " -samplegroup Testing " +
-        " -samplegroup UI " +
-        " -samplegroup Views " +
-        " -samplegroup Wearable -devsite -samplesdir development/samples/browseable ",
-}
-
-droiddoc {
-    name: "ds-docs-kt",
-    defaults: ["framework-dokka-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
-    ],
-    args: "-noJdkLink -links https://kotlinlang.org/api/latest/jvm/stdlib/^external/dokka/package-list " +
-    "-noStdlibLink",
-    proofread_file: "ds-dokka-proofread.txt",
-    dokka_enabled: true,
-}
-
-java_genrule {
-    name: "ds-docs",
-    tools: [
-        "zip2zip",
-        "merge_zips",
-    ],
-    srcs: [
-        ":ds-docs-java{.docs.zip}",
-        ":ds-docs-kt{.docs.zip}",
-    ],
-    out: ["ds-docs.zip"],
-    dist: {
-        targets: ["docs"],
-    },
-    cmd: "$(location zip2zip) -i $(location :ds-docs-kt{.docs.zip}) -o $(genDir)/ds-docs-kt-moved.zip **/*:en/reference/kotlin && " +
-         "$(location merge_zips) $(out) $(location :ds-docs-java{.docs.zip}) $(genDir)/ds-docs-kt-moved.zip",
-}
-
-java_genrule {
-    name: "ds-docs-switched",
-    tools: [
-        "switcher4",
-        "soong_zip",
-    ],
-    srcs: [
-        ":ds-docs-java{.docs.zip}",
-        ":ds-docs-kt{.docs.zip}",
-    ],
-    out: ["ds-docs-switched.zip"],
-    dist: {
-        targets: ["docs"],
-    },
-    cmd: "unzip $(location :ds-docs-java{.docs.zip}) -d $(genDir) && " +
-         "unzip $(location :ds-docs-kt{.docs.zip}) -d $(genDir)/en/reference/kotlin && " +
-         "SWITCHER=$$(cd $$(dirname $(location switcher4)) && pwd)/$$(basename $(location switcher4)) && " +
-         "(cd $(genDir)/en/reference && $$SWITCHER --work platform) && " +
-         "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)",
-}
-
-
-droiddoc {
-    name: "ds-static-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
-    ],
-    hdf: [
-        "android.whichdoc online",
-    ],
-    proofread_file: "ds-static-docs-proofrerad.txt",
-    args: framework_docs_only_args +
-        " -staticonly " +
-        " -toroot / " +
-        " -devsite " +
-        " -ignoreJdLinks ",
-}
-
-droiddoc {
-    name: "ds-ref-navtree-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
-    ],
-    hdf: [
-        "android.whichdoc online",
-    ],
-    proofread_file: "ds-ref-navtree-docs-proofrerad.txt",
-    args: framework_docs_only_args +
-        " -toroot / " +
-        " -atLinksNavtree " +
-        " -navtreeonly ",
-}
-
-droiddoc {
-    name: "online-sdk-dev-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
-    ],
-    hdf: [
-        "android.whichdoc online",
-        "android.hasSamples true",
-    ],
-    proofread_file: "online-sdk-dev-docs-proofrerad.txt",
-    args: framework_docs_only_args +
-        " -toroot / -samplegroup Admin " +
-        " -samplegroup Background " +
-        " -samplegroup Connectivity " +
-        " -samplegroup Content " +
-        " -samplegroup Input " +
-        " -samplegroup Media " +
-        " -samplegroup Notification " +
-        " -samplegroup RenderScript " +
-        " -samplegroup Security " +
-        " -samplegroup Sensors " +
-        " -samplegroup System " +
-        " -samplegroup Testing " +
-        " -samplegroup UI " +
-        " -samplegroup Views " +
-        " -samplegroup Wearable -samplesdir development/samples/browseable ",
-}
-
-droiddoc {
-    name: "hidden-docs",
-    defaults: ["framework-docs-default"],
-    srcs: [
-        ":framework-doc-stubs",
-    ],
-    proofread_file: "hidden-docs-proofrerad.txt",
-    args: framework_docs_only_args +
-        " -referenceonly " +
-        " -title \"Android SDK - Including hidden APIs.\"",
-}
-
-droidstubs {
-    name: "hwbinder-stubs-docs",
-    srcs: [
-        "core/java/android/os/HidlSupport.java",
-        "core/java/android/annotation/IntDef.java",
-        "core/java/android/annotation/IntRange.java",
-        "core/java/android/annotation/NonNull.java",
-        "core/java/android/annotation/SystemApi.java",
-        "core/java/android/os/HidlMemory.java",
-        "core/java/android/os/HwBinder.java",
-        "core/java/android/os/HwBlob.java",
-        "core/java/android/os/HwParcel.java",
-        "core/java/android/os/IHwBinder.java",
-        "core/java/android/os/IHwInterface.java",
-        "core/java/android/os/DeadObjectException.java",
-        "core/java/android/os/DeadSystemException.java",
-        "core/java/android/os/NativeHandle.java",
-        "core/java/android/os/RemoteException.java",
-        "core/java/android/util/AndroidException.java",
-    ],
-    installable: false,
-    sdk_version: "core_platform",
-    annotations_enabled: true,
-    previous_api: ":last-released-public-api",
-    merge_annotations_dirs: [
-        "metalava-manual",
-    ],
-    args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\)",
-}
-
-java_library_static {
-    name: "hwbinder.stubs",
-    sdk_version: "core_current",
-    srcs: [
-        ":hwbinder-stubs-docs",
-    ],
-}
-
-droidstubs {
-    name: "hiddenapi-lists-docs",
-    defaults: ["metalava-api-stubs-default"],
-    arg_files: [
-        "core/res/AndroidManifest.xml",
-    ],
-    dex_api_filename: "public-dex.txt",
-    private_dex_api_filename: "private-dex.txt",
-    removed_dex_api_filename: "removed-dex.txt",
-    args: metalava_framework_docs_args +
-        " --show-unannotated " +
-        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) " +
-        " --show-annotation android.annotation.TestApi ",
-}
-
-droidstubs {
-    name: "hiddenapi-mappings",
-    defaults: ["metalava-api-stubs-default"],
-    srcs: [
-        ":opt-telephony-common-srcs",
-    ],
-
-    arg_files: [
-        "core/res/AndroidManifest.xml",
-    ],
-    dex_mapping_filename: "dex-mapping.txt",
-    args: metalava_framework_docs_args +
-        " --hide ReferencesHidden " +
-        " --hide UnhiddenSystemApi " +
-        " --show-unannotated " +
-        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) " +
-        " --show-annotation android.annotation.TestApi ",
-}
-
-droidstubs {
-    name: "api-stubs-docs",
-    defaults: ["metalava-api-stubs-default"],
-    api_filename: "public_api.txt",
-    private_api_filename: "private.txt",
-    removed_api_filename: "removed.txt",
-    arg_files: [
-        "core/res/AndroidManifest.xml",
-    ],
-    args: metalava_framework_docs_args,
-    check_api: {
-        current: {
-            api_file: "api/current.txt",
-            removed_api_file: "api/removed.txt",
-        },
-        last_released: {
-            api_file: ":last-released-public-api",
-            removed_api_file: "api/removed.txt",
-            baseline_file: ":public-api-incompatibilities-with-last-released",
-        },
-        api_lint: {
-            enabled: true,
-            new_since: ":last-released-public-api",
-            baseline_file: "api/lint-baseline.txt",
-        },
-    },
-    jdiff_enabled: true,
-}
-
-droidstubs {
-    name: "system-api-stubs-docs",
-    defaults: ["metalava-api-stubs-default"],
-    api_tag_name: "SYSTEM",
-    api_filename: "system-api.txt",
-    private_api_filename: "system-private.txt",
-    private_dex_api_filename: "system-private-dex.txt",
-    removed_api_filename: "system-removed.txt",
-    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\\)",
-    check_api: {
-        current: {
-            api_file: "api/system-current.txt",
-            removed_api_file: "api/system-removed.txt",
-        },
-        last_released: {
-            api_file: ":last-released-system-api",
-            removed_api_file: "api/system-removed.txt",
-            baseline_file: ":system-api-incompatibilities-with-last-released"
-        },
-        api_lint: {
-            enabled: true,
-            new_since: ":last-released-system-api",
-            baseline_file: "api/system-lint-baseline.txt",
-        },
-    },
-    jdiff_enabled: true,
-}
-
-droidstubs {
-    name: "test-api-stubs-docs",
-    defaults: ["metalava-api-stubs-default"],
-    api_tag_name: "TEST",
-    api_filename: "test-api.txt",
-    removed_api_filename: "test-removed.txt",
-    arg_files: [
-        "core/res/AndroidManifest.xml",
-    ],
-    args: metalava_framework_docs_args + " --show-annotation android.annotation.TestApi",
-    check_api: {
-        current: {
-            api_file: "api/test-current.txt",
-            removed_api_file: "api/test-removed.txt",
-        },
-        api_lint: {
-            enabled: true,
-            baseline_file: "api/test-lint-baseline.txt",
-        },
-    },
-}
-
 filegroup {
     name: "framework-annotation-nonnull-srcs",
     srcs: [
@@ -1716,6 +1075,7 @@
 filegroup {
     name: "framework-telephony-stack-shared-srcs",
     srcs: [
+        "core/java/android/os/BasicShellCommandHandler.java",
         "core/java/android/os/RegistrantList.java",
         "core/java/android/os/Registrant.java",
         "core/java/android/util/LocalLog.java",
@@ -1778,3 +1138,20 @@
         "core/java/com/android/internal/util/XmlUtils.java",
     ],
 }
+
+// TODO(b/145644363): move this to under StubLibraries.bp or ApiDocs.bp
+metalava_framework_docs_args = "--manifest $(location core/res/AndroidManifest.xml) " +
+    "--ignore-classes-on-classpath " +
+    "--hide-package com.android.server " +
+    "--error UnhiddenSystemApi " +
+    "--hide RequiresPermission " +
+    "--hide CallbackInterface " +
+    "--hide MissingPermission --hide BroadcastBehavior " +
+    "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " +
+    "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo " +
+    "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.*"
+
+build = [
+    "StubLibraries.bp",
+    "ApiDocs.bp",
+]
diff --git a/ApiDocs.bp b/ApiDocs.bp
new file mode 100644
index 0000000..e373db6
--- /dev/null
+++ b/ApiDocs.bp
@@ -0,0 +1,436 @@
+// 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.
+
+// How API docs are generated:
+//
+// raw source files --(metalava)--> stub source files --(doclava)--> API doc
+//
+// The metalava conversion is done by droidstub modules framework-doc-*-stubs.
+// The API doc generation is done by the various droiddoc modules each of which
+// is for different format.
+
+/////////////////////////////////////////////////////////////////////
+// stub source files are generated using metalava
+/////////////////////////////////////////////////////////////////////
+
+framework_docs_only_libs = [
+    "voip-common",
+    "android.test.mock",
+    "android-support-annotations",
+    "android-support-compat",
+    "android-support-core-ui",
+    "android-support-core-utils",
+    "android-support-design",
+    "android-support-dynamic-animation",
+    "android-support-exifinterface",
+    "android-support-fragment",
+    "android-support-media-compat",
+    "android-support-percent",
+    "android-support-transition",
+    "android-support-v7-cardview",
+    "android-support-v7-gridlayout",
+    "android-support-v7-mediarouter",
+    "android-support-v7-palette",
+    "android-support-v7-preference",
+    "android-support-v13",
+    "android-support-v14-preference",
+    "android-support-v17-leanback",
+    "android-support-vectordrawable",
+    "android-support-animatedvectordrawable",
+    "android-support-v7-appcompat",
+    "android-support-v7-recyclerview",
+    "android-support-v8-renderscript",
+    "android-support-multidex",
+    "android-support-multidex-instrumentation",
+]
+
+stubs_defaults {
+    name: "framework-doc-stubs-default",
+    srcs: [
+        ":framework-mime-sources",
+        ":framework-non-updatable-sources",
+        ":framework-updatable-sources",
+        "core/java/**/*.logtags",
+        "test-base/src/**/*.java",
+        ":opt-telephony-srcs",
+        ":opt-net-voip-srcs",
+        ":core-current-stubs-source",
+        ":core_public_api_files",
+        "test-mock/src/**/*.java",
+        "test-runner/src/**/*.java",
+    ],
+    libs: framework_docs_only_libs,
+    create_doc_stubs: true,
+    annotations_enabled: true,
+    api_levels_annotations_enabled: true,
+    api_levels_annotations_dirs: [
+        "sdk-dir",
+        "api-versions-jars-dir",
+    ],
+    previous_api: ":last-released-public-api",
+    merge_annotations_dirs: [
+        "metalava-manual",
+    ],
+}
+
+droidstubs {
+    name: "framework-doc-stubs",
+    defaults: ["framework-doc-stubs-default"],
+    arg_files: [
+        "core/res/AndroidManifest.xml",
+    ],
+    args: metalava_framework_docs_args,
+    write_sdk_values: true,
+}
+
+droidstubs {
+    name: "framework-doc-system-stubs",
+    defaults: ["framework-doc-stubs-default"],
+    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\\) ",
+    write_sdk_values: true,
+}
+
+/////////////////////////////////////////////////////////////////////
+// API docs are created from the generated stub source files
+// using droiddoc
+/////////////////////////////////////////////////////////////////////
+
+framework_docs_only_args = " -android -manifest $(location core/res/AndroidManifest.xml) " +
+    "-werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 " +
+    "-overview $(location core/java/overview.html) " +
+    // Federate Support Library references against local API file.
+    "-federate SupportLib https://developer.android.com " +
+    "-federationapi SupportLib $(location :current-support-api) " +
+    // Federate Support Library references against local API file.
+    "-federate AndroidX https://developer.android.com " +
+    "-federationapi AndroidX $(location :current-androidx-api) "
+
+doc_defaults {
+    name: "framework-docs-default",
+    libs: framework_docs_only_libs +
+        ["stub-annotations"],
+    html_dirs: [
+        "docs/html",
+    ],
+    knowntags: [
+        "docs/knowntags.txt",
+        ":known-oj-tags",
+    ],
+    custom_template: "droiddoc-templates-sdk",
+    resourcesdir: "docs/html/reference/images/",
+    resourcesoutdir: "reference/android/images/",
+    hdf: [
+        "dac true",
+        "sdk.codename O",
+        "sdk.preview.version 1",
+        "sdk.version 7.0",
+        "sdk.rel.id 1",
+        "sdk.preview 0",
+    ],
+    arg_files: [
+        "core/res/AndroidManifest.xml",
+        "core/java/overview.html",
+        ":current-support-api",
+        ":current-androidx-api",
+    ],
+    create_stubs: false,
+}
+
+doc_defaults {
+    name: "framework-dokka-docs-default",
+    create_stubs: false,
+}
+
+droiddoc {
+    name: "doc-comment-check-docs",
+    defaults: ["framework-docs-default"],
+    srcs: [
+        ":framework-doc-stubs",
+    ],
+    args: framework_docs_only_args + " -referenceonly -parsecomments",
+    installable: false,
+}
+
+droiddoc {
+    name: "offline-sdk-docs",
+    defaults: ["framework-docs-default"],
+    srcs: [
+        ":framework-doc-stubs",
+    ],
+    hdf: [
+        "android.whichdoc offline",
+    ],
+    proofread_file: "offline-sdk-docs-proofrerad.txt",
+    args: framework_docs_only_args + " -offlinemode -title \"Android SDK\"",
+    static_doc_index_redirect: "docs/docs-preview-index.html",
+}
+
+droiddoc {
+    // Please sync with android-api-council@ before making any changes for the name property below.
+    // Since there's cron jobs that fetch offline-sdk-referenceonly-docs-docs.zip periodically.
+    // See b/116221385 for reference.
+    name: "offline-sdk-referenceonly-docs",
+    defaults: ["framework-docs-default"],
+    srcs: [
+        ":framework-doc-stubs",
+    ],
+    hdf: [
+        "android.whichdoc offline",
+    ],
+    proofread_file: "offline-sdk-referenceonly-docs-proofrerad.txt",
+    args: framework_docs_only_args + " -offlinemode -title \"Android SDK\" -referenceonly",
+    static_doc_index_redirect: "docs/docs-documentation-redirect.html",
+    static_doc_properties: "docs/source.properties",
+}
+
+droiddoc {
+    // Please sync with android-api-council@ before making any changes for the name property below.
+    // Since there's cron jobs that fetch offline-system-sdk-referenceonly-docs-docs.zip periodically.
+    // See b/116221385 for reference.
+    name: "offline-system-sdk-referenceonly-docs",
+    defaults: ["framework-docs-default"],
+    srcs: [
+        ":framework-doc-system-stubs",
+    ],
+    hdf: [
+        "android.whichdoc offline",
+    ],
+    proofread_file: "offline-system-sdk-referenceonly-docs-proofrerad.txt",
+    args: framework_docs_only_args + " -hide 101 -hide 104 -hide 108" +
+    " -offlinemode -title \"Android System SDK\" -referenceonly",
+    static_doc_index_redirect: "docs/docs-documentation-redirect.html",
+    static_doc_properties: "docs/source.properties",
+}
+
+droiddoc {
+    name: "online-sdk-docs",
+    defaults: ["framework-docs-default"],
+    srcs: [
+        ":framework-doc-stubs",
+    ],
+    hdf: [
+        "android.whichdoc online",
+        "android.hasSamples true",
+    ],
+    proofread_file: "online-sdk-docs-proofrerad.txt",
+    args: framework_docs_only_args +
+        " -toroot / -samplegroup Admin " +
+        " -samplegroup Background " +
+        " -samplegroup Connectivity " +
+        " -samplegroup Content " +
+        " -samplegroup Input " +
+        " -samplegroup Media " +
+        " -samplegroup Notification " +
+        " -samplegroup RenderScript " +
+        " -samplegroup Security " +
+        " -samplegroup Sensors " +
+        " -samplegroup System " +
+        " -samplegroup Testing " +
+        " -samplegroup UI " +
+        " -samplegroup Views " +
+        " -samplegroup Wearable -samplesdir development/samples/browseable ",
+}
+
+droiddoc {
+    name: "online-system-api-sdk-docs",
+    defaults: ["framework-docs-default"],
+    srcs: [
+        ":framework-doc-system-stubs",
+    ],
+    hdf: [
+        "android.whichdoc online",
+        "android.hasSamples true",
+    ],
+    proofread_file: "online-system-api-sdk-docs-proofrerad.txt",
+    args: framework_docs_only_args +
+        " -referenceonly " +
+        " -title \"Android SDK - Including system APIs.\" " +
+        " -hide 101 " +
+        " -hide 104 " +
+        " -hide 108 " +
+        " -toroot / -samplegroup Admin " +
+        " -samplegroup Background " +
+        " -samplegroup Connectivity " +
+        " -samplegroup Content " +
+        " -samplegroup Input " +
+        " -samplegroup Media " +
+        " -samplegroup Notification " +
+        " -samplegroup RenderScript " +
+        " -samplegroup Security " +
+        " -samplegroup Sensors " +
+        " -samplegroup System " +
+        " -samplegroup Testing " +
+        " -samplegroup UI " +
+        " -samplegroup Views " +
+        " -samplegroup Wearable -samplesdir development/samples/browseable ",
+    installable: false,
+}
+
+droiddoc {
+    name: "ds-docs-java",
+    defaults: ["framework-docs-default"],
+    srcs: [
+        ":framework-doc-stubs",
+    ],
+    hdf: [
+        "android.whichdoc online",
+        "android.hasSamples true",
+    ],
+    proofread_file: "ds-docs-proofrerad.txt",
+    args: framework_docs_only_args +
+        " -toroot / -yamlV2 -metalavaApiSince -samplegroup Admin " +
+        " -samplegroup Background " +
+        " -samplegroup Connectivity " +
+        " -samplegroup Content " +
+        " -samplegroup Input " +
+        " -samplegroup Media " +
+        " -samplegroup Notification " +
+        " -samplegroup RenderScript " +
+        " -samplegroup Security " +
+        " -samplegroup Sensors " +
+        " -samplegroup System " +
+        " -samplegroup Testing " +
+        " -samplegroup UI " +
+        " -samplegroup Views " +
+        " -samplegroup Wearable -devsite -samplesdir development/samples/browseable ",
+}
+
+droiddoc {
+    name: "ds-docs-kt",
+    defaults: ["framework-dokka-docs-default"],
+    srcs: [
+        ":framework-doc-stubs",
+    ],
+    args: "-noJdkLink -links https://kotlinlang.org/api/latest/jvm/stdlib/^external/dokka/package-list " +
+    "-noStdlibLink",
+    proofread_file: "ds-dokka-proofread.txt",
+    dokka_enabled: true,
+}
+
+java_genrule {
+    name: "ds-docs",
+    tools: [
+        "zip2zip",
+        "merge_zips",
+    ],
+    srcs: [
+        ":ds-docs-java{.docs.zip}",
+        ":ds-docs-kt{.docs.zip}",
+    ],
+    out: ["ds-docs.zip"],
+    dist: {
+        targets: ["docs"],
+    },
+    cmd: "$(location zip2zip) -i $(location :ds-docs-kt{.docs.zip}) -o $(genDir)/ds-docs-kt-moved.zip **/*:en/reference/kotlin && " +
+         "$(location merge_zips) $(out) $(location :ds-docs-java{.docs.zip}) $(genDir)/ds-docs-kt-moved.zip",
+}
+
+java_genrule {
+    name: "ds-docs-switched",
+    tools: [
+        "switcher4",
+        "soong_zip",
+    ],
+    srcs: [
+        ":ds-docs-java{.docs.zip}",
+        ":ds-docs-kt{.docs.zip}",
+    ],
+    out: ["ds-docs-switched.zip"],
+    dist: {
+        targets: ["docs"],
+    },
+    cmd: "unzip $(location :ds-docs-java{.docs.zip}) -d $(genDir) && " +
+         "unzip $(location :ds-docs-kt{.docs.zip}) -d $(genDir)/en/reference/kotlin && " +
+         "SWITCHER=$$(cd $$(dirname $(location switcher4)) && pwd)/$$(basename $(location switcher4)) && " +
+         "(cd $(genDir)/en/reference && $$SWITCHER --work platform) && " +
+         "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)",
+}
+
+droiddoc {
+    name: "ds-static-docs",
+    defaults: ["framework-docs-default"],
+    srcs: [
+        ":framework-doc-stubs",
+    ],
+    hdf: [
+        "android.whichdoc online",
+    ],
+    proofread_file: "ds-static-docs-proofrerad.txt",
+    args: framework_docs_only_args +
+        " -staticonly " +
+        " -toroot / " +
+        " -devsite " +
+        " -ignoreJdLinks ",
+}
+
+droiddoc {
+    name: "ds-ref-navtree-docs",
+    defaults: ["framework-docs-default"],
+    srcs: [
+        ":framework-doc-stubs",
+    ],
+    hdf: [
+        "android.whichdoc online",
+    ],
+    proofread_file: "ds-ref-navtree-docs-proofrerad.txt",
+    args: framework_docs_only_args +
+        " -toroot / " +
+        " -atLinksNavtree " +
+        " -navtreeonly ",
+}
+
+droiddoc {
+    name: "online-sdk-dev-docs",
+    defaults: ["framework-docs-default"],
+    srcs: [
+        ":framework-doc-stubs",
+    ],
+    hdf: [
+        "android.whichdoc online",
+        "android.hasSamples true",
+    ],
+    proofread_file: "online-sdk-dev-docs-proofrerad.txt",
+    args: framework_docs_only_args +
+        " -toroot / -samplegroup Admin " +
+        " -samplegroup Background " +
+        " -samplegroup Connectivity " +
+        " -samplegroup Content " +
+        " -samplegroup Input " +
+        " -samplegroup Media " +
+        " -samplegroup Notification " +
+        " -samplegroup RenderScript " +
+        " -samplegroup Security " +
+        " -samplegroup Sensors " +
+        " -samplegroup System " +
+        " -samplegroup Testing " +
+        " -samplegroup UI " +
+        " -samplegroup Views " +
+        " -samplegroup Wearable -samplesdir development/samples/browseable ",
+}
+
+droiddoc {
+    name: "hidden-docs",
+    defaults: ["framework-docs-default"],
+    srcs: [
+        ":framework-doc-stubs",
+    ],
+    proofread_file: "hidden-docs-proofrerad.txt",
+    args: framework_docs_only_args +
+        " -referenceonly " +
+        " -title \"Android SDK - Including hidden APIs.\"",
+}
+
diff --git a/StubLibraries.bp b/StubLibraries.bp
new file mode 100644
index 0000000..78f1b9c
--- /dev/null
+++ b/StubLibraries.bp
@@ -0,0 +1,340 @@
+// 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.
+
+// How stubs are generated:
+//
+// raw source files --(metalava)--> stub source files --(javac)--> stub jar files
+//
+// The metalava conversion is done by droidstub modules *-api-stubs-docs.
+// The javac compilation is done by java_library modules android_*_stubs_current.
+// The metalava conversion is also responsible for creating API signature files
+// and comparing them against the last API signature in api/*-current.txt files
+// and also against the latest frozen API signature in prebuilts/sdk/*/*/api/android.txt
+// files.
+
+/////////////////////////////////////////////////////////////////////
+// Common metalava configs
+/////////////////////////////////////////////////////////////////////
+
+packages_to_document = [
+    "android",
+    "dalvik",
+    "java",
+    "javax",
+    "junit",
+    "org.apache.http",
+    "org.json",
+    "org.w3c.dom",
+    "org.xml.sax",
+    "org.xmlpull",
+]
+
+stubs_defaults {
+    name: "metalava-api-stubs-default",
+    srcs: [
+        ":framework-non-updatable-sources",
+        ":framework-updatable-sources",
+        "core/java/**/*.logtags",
+        ":opt-telephony-srcs",
+        ":opt-net-voip-srcs",
+        ":core-current-stubs-source",
+        ":core_public_api_files",
+        ":ike-api-srcs",
+    ],
+    libs: ["framework-internal-utils"],
+    installable: false,
+    annotations_enabled: true,
+    previous_api: ":last-released-public-api",
+    merge_annotations_dirs: [
+        "metalava-manual",
+    ],
+    api_levels_annotations_enabled: true,
+    api_levels_annotations_dirs: [
+        "sdk-dir",
+        "api-versions-jars-dir",
+    ],
+    sdk_version: "core_platform",
+    filter_packages: packages_to_document,
+}
+
+/////////////////////////////////////////////////////////////////////
+// *-api-stubs-docs modules providing source files for the stub libraries
+/////////////////////////////////////////////////////////////////////
+
+droidstubs {
+    name: "api-stubs-docs",
+    defaults: ["metalava-api-stubs-default"],
+    api_filename: "public_api.txt",
+    private_api_filename: "private.txt",
+    removed_api_filename: "removed.txt",
+    arg_files: [
+        "core/res/AndroidManifest.xml",
+    ],
+    args: metalava_framework_docs_args,
+    check_api: {
+        current: {
+            api_file: "api/current.txt",
+            removed_api_file: "api/removed.txt",
+        },
+        last_released: {
+            api_file: ":last-released-public-api",
+            removed_api_file: "api/removed.txt",
+            baseline_file: ":public-api-incompatibilities-with-last-released",
+        },
+        api_lint: {
+            enabled: true,
+            new_since: ":last-released-public-api",
+            baseline_file: "api/lint-baseline.txt",
+        },
+    },
+    jdiff_enabled: true,
+}
+
+droidstubs {
+    name: "system-api-stubs-docs",
+    defaults: ["metalava-api-stubs-default"],
+    api_tag_name: "SYSTEM",
+    api_filename: "system-api.txt",
+    private_api_filename: "system-private.txt",
+    private_dex_api_filename: "system-private-dex.txt",
+    removed_api_filename: "system-removed.txt",
+    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\\)",
+    check_api: {
+        current: {
+            api_file: "api/system-current.txt",
+            removed_api_file: "api/system-removed.txt",
+        },
+        last_released: {
+            api_file: ":last-released-system-api",
+            removed_api_file: "api/system-removed.txt",
+            baseline_file: ":system-api-incompatibilities-with-last-released"
+        },
+        api_lint: {
+            enabled: true,
+            new_since: ":last-released-system-api",
+            baseline_file: "api/system-lint-baseline.txt",
+        },
+    },
+    jdiff_enabled: true,
+}
+
+droidstubs {
+    name: "test-api-stubs-docs",
+    defaults: ["metalava-api-stubs-default"],
+    api_tag_name: "TEST",
+    api_filename: "test-api.txt",
+    removed_api_filename: "test-removed.txt",
+    arg_files: [
+        "core/res/AndroidManifest.xml",
+    ],
+    args: metalava_framework_docs_args + " --show-annotation android.annotation.TestApi",
+    check_api: {
+        current: {
+            api_file: "api/test-current.txt",
+            removed_api_file: "api/test-removed.txt",
+        },
+        api_lint: {
+            enabled: true,
+            baseline_file: "api/test-lint-baseline.txt",
+        },
+    },
+}
+
+/////////////////////////////////////////////////////////////////////
+// android_*_stubs_current modules are the stubs libraries compiled
+// from *-api-stubs-docs
+/////////////////////////////////////////////////////////////////////
+
+java_defaults {
+    name: "framework-stubs-default",
+    errorprone: {
+        javacflags: [
+            "-XepDisableAllChecks",
+        ],
+    },
+    java_resources: [
+        ":notices-for-framework-stubs",
+    ],
+    sdk_version: "core_current",
+    system_modules: "none",
+    java_version: "1.8",
+    compile_dex: true,
+}
+
+java_library_static {
+    name: "android_stubs_current",
+    srcs: [
+        ":api-stubs-docs",
+    ],
+    libs: [
+        "stub-annotations",
+    ],
+    static_libs: [
+        "private-stub-annotations-jar",
+    ],
+    defaults: ["framework-stubs-default"],
+}
+
+java_library_static {
+    name: "android_system_stubs_current",
+    srcs: [
+        ":system-api-stubs-docs",
+    ],
+    libs: [
+        "stub-annotations",
+    ],
+    static_libs: [
+        "private-stub-annotations-jar",
+    ],
+    defaults: ["framework-stubs-default"],
+}
+
+java_library_static {
+    name: "android_test_stubs_current",
+    srcs: [
+        ":test-api-stubs-docs",
+    ],
+    libs: [
+        "stub-annotations",
+    ],
+    static_libs: [
+        "private-stub-annotations-jar",
+    ],
+    defaults: ["framework-stubs-default"],
+}
+
+java_system_modules {
+    name: "android_stubs_current_system_modules",
+    libs: ["android_stubs_current"],
+}
+
+java_system_modules {
+    name: "android_system_stubs_current_system_modules",
+    libs: ["android_system_stubs_current"],
+}
+
+java_system_modules {
+    name: "android_test_stubs_current_system_modules",
+    libs: ["android_test_stubs_current"],
+}
+
+/////////////////////////////////////////////////////////////////////
+// hwbinder.stubs provides APIs required for building HIDL Java
+// libraries.
+/////////////////////////////////////////////////////////////////////
+
+droidstubs {
+    name: "hwbinder-stubs-docs",
+    srcs: [
+        "core/java/android/os/HidlSupport.java",
+        "core/java/android/annotation/IntDef.java",
+        "core/java/android/annotation/IntRange.java",
+        "core/java/android/annotation/NonNull.java",
+        "core/java/android/annotation/SystemApi.java",
+        "core/java/android/os/HidlMemory.java",
+        "core/java/android/os/HwBinder.java",
+        "core/java/android/os/HwBlob.java",
+        "core/java/android/os/HwParcel.java",
+        "core/java/android/os/IHwBinder.java",
+        "core/java/android/os/IHwInterface.java",
+        "core/java/android/os/DeadObjectException.java",
+        "core/java/android/os/DeadSystemException.java",
+        "core/java/android/os/NativeHandle.java",
+        "core/java/android/os/RemoteException.java",
+        "core/java/android/util/AndroidException.java",
+    ],
+    installable: false,
+    sdk_version: "core_platform",
+    annotations_enabled: true,
+    previous_api: ":last-released-public-api",
+    merge_annotations_dirs: [
+        "metalava-manual",
+    ],
+    args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\)",
+}
+
+java_library_static {
+    name: "hwbinder.stubs",
+    sdk_version: "core_current",
+    srcs: [
+        ":hwbinder-stubs-docs",
+    ],
+}
+
+/////////////////////////////////////////////////////////////////////
+// Stubs for hiddenapi processing.
+/////////////////////////////////////////////////////////////////////
+
+droidstubs {
+    name: "hiddenapi-lists-docs",
+    defaults: ["metalava-api-stubs-default"],
+    arg_files: [
+        "core/res/AndroidManifest.xml",
+    ],
+    dex_api_filename: "public-dex.txt",
+    private_dex_api_filename: "private-dex.txt",
+    removed_dex_api_filename: "removed-dex.txt",
+    args: metalava_framework_docs_args +
+        " --show-unannotated " +
+        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) " +
+        " --show-annotation android.annotation.TestApi ",
+}
+
+droidstubs {
+    name: "hiddenapi-mappings",
+    defaults: ["metalava-api-stubs-default"],
+    srcs: [
+        ":opt-telephony-common-srcs",
+    ],
+
+    arg_files: [
+        "core/res/AndroidManifest.xml",
+    ],
+    dex_mapping_filename: "dex-mapping.txt",
+    args: metalava_framework_docs_args +
+        " --hide ReferencesHidden " +
+        " --hide UnhiddenSystemApi " +
+        " --show-unannotated " +
+        " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) " +
+        " --show-annotation android.annotation.TestApi ",
+}
+
+/////////////////////////////////////////////////////////////////////
+// api/*-current.txt files for use by modules in other directories
+// like the CTS test
+/////////////////////////////////////////////////////////////////////
+
+filegroup {
+    name: "frameworks-base-api-current.txt",
+    srcs: [
+        "api/current.txt",
+    ],
+}
+
+filegroup {
+    name: "frameworks-base-api-system-current.txt",
+    srcs: [
+        "api/system-current.txt",
+    ],
+}
+
+filegroup {
+    name: "frameworks-base-api-system-removed.txt",
+    srcs: [
+        "api/system-removed.txt",
+    ],
+}
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
index 37b07a6..a2b0577 100644
--- a/apex/statsd/framework/Android.bp
+++ b/apex/statsd/framework/Android.bp
@@ -24,7 +24,7 @@
     name: "framework-statsd",
     installable: true,
     // TODO(b/146209659): Use system_current instead.
-    sdk_version: "core_platform",
+    sdk_version: "core_current",
     srcs: [
         ":framework-statsd-sources",
     ],
diff --git a/api/current.txt b/api/current.txt
index d7176af..0589537 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1062,6 +1062,7 @@
     field public static final int popupWindowStyle = 16842870; // 0x1010076
     field public static final int port = 16842793; // 0x1010029
     field public static final int positiveButtonText = 16843253; // 0x10101f5
+    field public static final int preferMinimalPostProcessing = 16844300; // 0x101060c
     field public static final int preferenceCategoryStyle = 16842892; // 0x101008c
     field public static final int preferenceFragmentStyle = 16844038; // 0x1010506
     field public static final int preferenceInformationStyle = 16842893; // 0x101008d
@@ -3812,6 +3813,7 @@
     method public void onPerformDirectAction(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.os.Bundle>);
     method public void onPictureInPictureModeChanged(boolean, android.content.res.Configuration);
     method @Deprecated public void onPictureInPictureModeChanged(boolean);
+    method public void onPictureInPictureRequested();
     method @CallSuper protected void onPostCreate(@Nullable android.os.Bundle);
     method public void onPostCreate(@Nullable android.os.Bundle, @Nullable android.os.PersistableBundle);
     method @CallSuper protected void onPostResume();
@@ -5102,6 +5104,7 @@
     method public void callActivityOnDestroy(android.app.Activity);
     method public void callActivityOnNewIntent(android.app.Activity, android.content.Intent);
     method public void callActivityOnPause(android.app.Activity);
+    method public void callActivityOnPictureInPictureRequested(@NonNull android.app.Activity);
     method public void callActivityOnPostCreate(@NonNull android.app.Activity, @Nullable android.os.Bundle);
     method public void callActivityOnPostCreate(@NonNull android.app.Activity, @Nullable android.os.Bundle, @Nullable android.os.PersistableBundle);
     method public void callActivityOnRestart(android.app.Activity);
@@ -9973,6 +9976,7 @@
     field public static final String DOWNLOAD_SERVICE = "download";
     field public static final String DROPBOX_SERVICE = "dropbox";
     field public static final String EUICC_SERVICE = "euicc";
+    field public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
     field public static final String FINGERPRINT_SERVICE = "fingerprint";
     field public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
     field public static final String INPUT_METHOD_SERVICE = "input_method";
@@ -11189,6 +11193,7 @@
     field public String parentActivityName;
     field public String permission;
     field public int persistableMode;
+    field public boolean preferMinimalPostProcessing;
     field public int screenOrientation;
     field public int softInputMode;
     field public String targetActivity;
@@ -23743,6 +23748,7 @@
     field public static final int ENCODING_IEC61937 = 13; // 0xd
     field public static final int ENCODING_INVALID = 0; // 0x0
     field public static final int ENCODING_MP3 = 9; // 0x9
+    field public static final int ENCODING_OPUS = 20; // 0x14
     field public static final int ENCODING_PCM_16BIT = 2; // 0x2
     field public static final int ENCODING_PCM_8BIT = 3; // 0x3
     field public static final int ENCODING_PCM_FLOAT = 4; // 0x4
@@ -30152,6 +30158,21 @@
 
 package android.net.wifi {
 
+  public abstract class EasyConnectStatusCallback {
+    field public static final int EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION = -2; // 0xfffffffe
+    field public static final int EASY_CONNECT_EVENT_FAILURE_BUSY = -5; // 0xfffffffb
+    field public static final int EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK = -10; // 0xfffffff6
+    field public static final int EASY_CONNECT_EVENT_FAILURE_CONFIGURATION = -4; // 0xfffffffc
+    field public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION = -11; // 0xfffffff5
+    field public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION = -12; // 0xfffffff4
+    field public static final int EASY_CONNECT_EVENT_FAILURE_GENERIC = -7; // 0xfffffff9
+    field public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK = -9; // 0xfffffff7
+    field public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_URI = -1; // 0xffffffff
+    field public static final int EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE = -3; // 0xfffffffd
+    field public static final int EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED = -8; // 0xfffffff8
+    field public static final int EASY_CONNECT_EVENT_FAILURE_TIMEOUT = -6; // 0xfffffffa
+  }
+
   public class ScanResult implements android.os.Parcelable {
     method public int describeContents();
     method public int getWifiStandard();
@@ -39453,6 +39474,10 @@
     field public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
     field public static final String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
     field public static final String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
+    field public static final String EXTRA_EASY_CONNECT_ATTEMPTED_SSID = "android.provider.extra.EASY_CONNECT_ATTEMPTED_SSID";
+    field public static final String EXTRA_EASY_CONNECT_BAND_LIST = "android.provider.extra.EASY_CONNECT_BAND_LIST";
+    field public static final String EXTRA_EASY_CONNECT_CHANNEL_LIST = "android.provider.extra.EASY_CONNECT_CHANNEL_LIST";
+    field public static final String EXTRA_EASY_CONNECT_ERROR_CODE = "android.provider.extra.EASY_CONNECT_ERROR_CODE";
     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";
@@ -41388,6 +41413,11 @@
     method public android.security.ConfirmationPrompt.Builder setPromptText(CharSequence);
   }
 
+  public final class FileIntegrityManager {
+    method public boolean isApkVeritySupported();
+    method @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public boolean isAppSourceCertificateTrusted(@NonNull java.security.cert.X509Certificate) throws java.security.cert.CertificateEncodingException;
+  }
+
   public final class KeyChain {
     ctor public KeyChain();
     method public static void choosePrivateKeyAlias(@NonNull android.app.Activity, @NonNull android.security.KeyChainAliasCallback, @Nullable String[], @Nullable java.security.Principal[], @Nullable String, int, @Nullable String);
@@ -44965,6 +44995,8 @@
     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_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";
     field public static final String KEY_MMS_ALIAS_ENABLED_BOOL = "aliasEnabled";
     field public static final String KEY_MMS_ALIAS_MAX_CHARS_INT = "aliasMaxChars";
@@ -45744,6 +45776,7 @@
     method public byte[] getPdu();
     method public int getProtocolIdentifier();
     method public String getPseudoSubject();
+    method @Nullable public String getRecipientAddress();
     method public String getServiceCenterAddress();
     method public int getStatus();
     method public int getStatusOnIcc();
@@ -49894,6 +49927,7 @@
     method @Deprecated public float[] getSupportedRefreshRates();
     method @Deprecated public int getWidth();
     method public boolean isHdr();
+    method public boolean isMinimalPostProcessingSupported();
     method public boolean isValid();
     method public boolean isWideColorGamut();
     field public static final int DEFAULT_DISPLAY = 0; // 0x0
@@ -52926,6 +52960,7 @@
     method public abstract void setNavigationBarColor(@ColorInt int);
     method public void setNavigationBarContrastEnforced(boolean);
     method public void setNavigationBarDividerColor(@ColorInt int);
+    method public void setPreferMinimalPostProcessing(boolean);
     method public void setReenterTransition(android.transition.Transition);
     method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable);
     method public final void setRestrictedCaptionAreaListener(android.view.Window.OnRestrictedCaptionAreaChangedListener);
@@ -53313,6 +53348,7 @@
     field public int layoutInDisplayCutoutMode;
     field @Deprecated public int memoryType;
     field public String packageName;
+    field public boolean preferMinimalPostProcessing;
     field public int preferredDisplayModeId;
     field @Deprecated public float preferredRefreshRate;
     field public int rotationAnimation;
@@ -54325,6 +54361,7 @@
   public static final class InlinePresentationSpec.Builder {
     ctor public InlinePresentationSpec.Builder(@NonNull android.util.Size, @NonNull android.util.Size);
     method @NonNull public android.view.inline.InlinePresentationSpec build();
+    method @NonNull public android.view.inline.InlinePresentationSpec.Builder setStyle(@Nullable String);
   }
 
 }
@@ -55262,6 +55299,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 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
@@ -55288,6 +55326,7 @@
     method @Nullable public android.os.LocaleList getDefaultLocales();
     method @Nullable public android.view.textclassifier.TextClassifier.EntityConfig getEntityConfig();
     method @NonNull public android.os.Bundle getExtras();
+    method @Nullable public java.time.ZonedDateTime getReferenceTime();
     method @NonNull public CharSequence getText();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLinks.Request> CREATOR;
@@ -55299,6 +55338,7 @@
     method @NonNull public android.view.textclassifier.TextLinks.Request.Builder setDefaultLocales(@Nullable android.os.LocaleList);
     method @NonNull public android.view.textclassifier.TextLinks.Request.Builder setEntityConfig(@Nullable android.view.textclassifier.TextClassifier.EntityConfig);
     method public android.view.textclassifier.TextLinks.Request.Builder setExtras(@Nullable android.os.Bundle);
+    method @NonNull public android.view.textclassifier.TextLinks.Request.Builder setReferenceTime(@Nullable java.time.ZonedDateTime);
   }
 
   public static final class TextLinks.TextLink implements android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index dfdcb19..d3f4ac4 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8,6 +8,7 @@
     field public static final String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES";
     field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO";
     field public static final String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS";
+    field public static final String ACCESS_MESSAGES_ON_ICC = "android.permission.ACCESS_MESSAGES_ON_ICC";
     field public static final String ACCESS_MOCK_LOCATION = "android.permission.ACCESS_MOCK_LOCATION";
     field public static final String ACCESS_MTP = "android.permission.ACCESS_MTP";
     field public static final String ACCESS_NETWORK_CONDITIONS = "android.permission.ACCESS_NETWORK_CONDITIONS";
@@ -777,6 +778,7 @@
     method @Nullable public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException;
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public int getUserProvisioningState();
+    method public boolean hasDeviceIdentifierAccess(@NonNull String, int, int);
     method public boolean isDeviceManaged();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied();
@@ -4066,6 +4068,7 @@
     method public android.media.PlayerProxy getPlayerProxy();
     method public int getPlayerState();
     method public int getPlayerType();
+    method public boolean isActive();
     field public static final int PLAYER_STATE_IDLE = 1; // 0x1
     field public static final int PLAYER_STATE_PAUSED = 3; // 0x3
     field public static final int PLAYER_STATE_RELEASED = 0; // 0x0
@@ -4728,6 +4731,13 @@
     method public void setValidatedPrivateDnsServers(@NonNull java.util.Collection<java.net.InetAddress>);
   }
 
+  public final class MatchAllNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+    ctor public MatchAllNetworkSpecifier();
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.MatchAllNetworkSpecifier> CREATOR;
+  }
+
   public class Network implements android.os.Parcelable {
     ctor public Network(@NonNull android.net.Network);
     method @NonNull public android.net.Network getPrivateDnsBypassingCopy();
@@ -5393,18 +5403,6 @@
     method public void onFailure(int);
     method public void onFailure(int, @Nullable String, @NonNull android.util.SparseArray<int[]>, @NonNull int[]);
     method public abstract void onProgress(int);
-    field public static final int EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION = -2; // 0xfffffffe
-    field public static final int EASY_CONNECT_EVENT_FAILURE_BUSY = -5; // 0xfffffffb
-    field public static final int EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK = -10; // 0xfffffff6
-    field public static final int EASY_CONNECT_EVENT_FAILURE_CONFIGURATION = -4; // 0xfffffffc
-    field public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION = -11; // 0xfffffff5
-    field public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION = -12; // 0xfffffff4
-    field public static final int EASY_CONNECT_EVENT_FAILURE_GENERIC = -7; // 0xfffffff9
-    field public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK = -9; // 0xfffffff7
-    field public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_URI = -1; // 0xffffffff
-    field public static final int EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE = -3; // 0xfffffffd
-    field public static final int EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED = -8; // 0xfffffff8
-    field public static final int EASY_CONNECT_EVENT_FAILURE_TIMEOUT = -6; // 0xfffffffa
     field public static final int EASY_CONNECT_EVENT_PROGRESS_AUTHENTICATION_SUCCESS = 0; // 0x0
     field public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_ACCEPTED = 3; // 0x3
     field public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE = 2; // 0x2
@@ -5610,11 +5608,19 @@
     field public static final int KEY_MGMT_WAPI_PSK = 13; // 0xd
   }
 
+  public final class SoftApCapability implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getMaxSupportedClients();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApCapability> CREATOR;
+  }
+
   public final class SoftApConfiguration implements android.os.Parcelable {
     method public int describeContents();
     method public int getBand();
     method @Nullable public android.net.MacAddress getBssid();
     method public int getChannel();
+    method public int getMaxNumberOfClients();
     method public int getSecurityType();
     method @Nullable public String getSsid();
     method @Nullable public String getWpa2Passphrase();
@@ -5637,6 +5643,7 @@
     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 setHiddenSsid(boolean);
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setSsid(@Nullable String);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setWpa2Passphrase(@Nullable String);
   }
@@ -5870,6 +5877,7 @@
     field public static final int PASSPOINT_ROAMING_NETWORK = 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
     field public static final String WIFI_AP_STATE_CHANGED_ACTION = "android.net.wifi.WIFI_AP_STATE_CHANGED";
     field public static final int WIFI_AP_STATE_DISABLED = 11; // 0xb
     field public static final int WIFI_AP_STATE_DISABLING = 10; // 0xa
@@ -5908,6 +5916,7 @@
   }
 
   public static interface WifiManager.SoftApCallback {
+    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);
     method public default void onStateChanged(int, int);
@@ -8910,6 +8919,11 @@
     field public static final String KEY_SUPPORT_CDMA_1X_VOICE_CALLS_BOOL = "support_cdma_1x_voice_calls_bool";
   }
 
+  public static final class CarrierConfigManager.Wifi {
+    field public static final String KEY_HOTSPOT_MAX_CLIENT_COUNT = "wifi.hotspot_maximum_client_count";
+    field public static final String KEY_PREFIX = "wifi.";
+  }
+
   public final class CarrierRestrictionRules implements android.os.Parcelable {
     method @NonNull public java.util.List<java.lang.Boolean> areCarrierIdentifiersAllowed(@NonNull java.util.List<android.service.carrier.CarrierIdentifier>);
     method public int describeContents();
@@ -9620,6 +9634,7 @@
   }
 
   public final class PreciseDataConnectionState implements android.os.Parcelable {
+    ctor public PreciseDataConnectionState(int, int, int, @NonNull String, @Nullable android.net.LinkProperties, int, @Nullable android.telephony.data.ApnSetting);
     method @Deprecated @NonNull public String getDataConnectionApn();
     method @Deprecated public int getDataConnectionApnTypeBitMask();
     method @Deprecated public int getDataConnectionFailCause();
@@ -9863,8 +9878,10 @@
   }
 
   public final class SmsManager {
+    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 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 @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>);
@@ -9890,6 +9907,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultVoiceSubscriptionId(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPreferredDataSubscriptionId(int, boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSubscriptionEnabled(int, boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUiccApplicationsEnabled(boolean, int);
     field @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS) public static final String ACTION_SUBSCRIPTION_PLANS_CHANGED = "android.telephony.action.SUBSCRIPTION_PLANS_CHANGED";
     field @NonNull public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
     field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff
@@ -9972,6 +9990,7 @@
     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();
     method public static long getMaxNumberVerificationTimeoutMillis();
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getMergedImsisFromGroup();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getNetworkCountryIso(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getPreferredNetworkTypeBitmask();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public int getRadioPowerState();
diff --git a/api/test-current.txt b/api/test-current.txt
index 08a2160..3bf2a85 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -105,6 +105,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public boolean moveTopActivityToPinnedStack(int, android.graphics.Rect);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void removeStacksInWindowingModes(int[]) throws java.lang.SecurityException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void removeStacksWithActivityTypes(int[]) throws java.lang.SecurityException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void requestPictureInPictureMode(@NonNull android.os.IBinder);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizeDockedStack(android.graphics.Rect, android.graphics.Rect);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizePinnedStack(int, android.graphics.Rect, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void resizeTask(int, android.graphics.Rect);
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index afff614..887d17c 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -409,7 +409,7 @@
 // ====  java proto device library (for test only)  ==============================
 java_library {
     name: "statsdprotolite",
-    sdk_version: "core_platform",
+    sdk_version: "core_current",
     proto: {
         type: "lite",
         include_dirs: ["external/protobuf/src"],
diff --git a/core/java/android/annotation/CallbackExecutor.java b/core/java/android/annotation/CallbackExecutor.java
index 5671a3d..4258f73 100644
--- a/core/java/android/annotation/CallbackExecutor.java
+++ b/core/java/android/annotation/CallbackExecutor.java
@@ -19,9 +19,6 @@
 import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
-import android.content.Context;
-import android.os.AsyncTask;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 import java.util.concurrent.Executor;
@@ -30,9 +27,10 @@
  * @paramDoc Callback and listener events are dispatched through this
  *           {@link Executor}, providing an easy way to control which thread is
  *           used. To dispatch events through the main thread of your
- *           application, you can use {@link Context#getMainExecutor()}. To
- *           dispatch events through a shared thread pool, you can use
- *           {@link AsyncTask#THREAD_POOL_EXECUTOR}.
+ *           application, you can use
+ *           {@link android.content.Context#getMainExecutor() Context.getMainExecutor()}.
+ *           To dispatch events through a shared thread pool, you can use
+ *           {@link android.os.AsyncTask#THREAD_POOL_EXECUTOR AsyncTask#THREAD_POOL_EXECUTOR}.
  * @hide
  */
 @Retention(SOURCE)
diff --git a/core/java/android/annotation/RequiresPermission.java b/core/java/android/annotation/RequiresPermission.java
index e5c0654..1d89e31 100644
--- a/core/java/android/annotation/RequiresPermission.java
+++ b/core/java/android/annotation/RequiresPermission.java
@@ -15,9 +15,6 @@
  */
 package android.annotation;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
 import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
 import static java.lang.annotation.ElementType.CONSTRUCTOR;
 import static java.lang.annotation.ElementType.FIELD;
@@ -25,6 +22,9 @@
 import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
 /**
  * Denotes that the annotated element requires (or may require) one or more permissions.
  * <p/>
@@ -55,7 +55,8 @@
  * <p>
  * When specified on a parameter, the annotation indicates that the method requires
  * a permission which depends on the value of the parameter. For example, consider
- * {@link android.app.Activity#startActivity(android.content.Intent)}:
+ * {@link android.app.Activity#startActivity(android.content.Intent)
+ * Activity#startActivity(Intent)}:
  * <pre>{@code
  *   public void startActivity(@RequiresPermission Intent intent) { ... }
  * }</pre>
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 9e0c2fc..070a4f8 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2837,6 +2837,17 @@
         return getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE);
     }
 
+    /**
+     * Called by the system when picture in picture mode should be entered if supported.
+     */
+    public void onPictureInPictureRequested() {
+        // Previous recommendation was for apps to enter picture-in-picture in onUserLeaveHint()
+        // which is sent after onPause(). This new method allows the system to request the app to
+        // go into picture-in-picture decoupling it from life cycle events. For backwards
+        // compatibility we schedule the life cycle events if the app didn't override this method.
+        mMainThread.schedulePauseAndReturnToCurrentState(mToken);
+    }
+
     void dispatchMovedToDisplay(int displayId, Configuration config) {
         updateDisplay(displayId);
         onMovedToDisplay(displayId, config);
@@ -7871,6 +7882,7 @@
         mCurrentConfig = config;
 
         mWindow.setColorMode(info.colorMode);
+        mWindow.setPreferMinimalPostProcessing(info.preferMinimalPostProcessing);
 
         setAutofillOptions(application.getAutofillOptions());
         setContentCaptureOptions(application.getContentCaptureOptions());
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 122004c..dd9a2bc 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -433,4 +434,18 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Requests that an activity should enter picture-in-picture mode if possible.
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void requestPictureInPictureMode(@NonNull IBinder token) {
+        try {
+            getService().requestPictureInPictureMode(token);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index be14556..08f8734 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -41,8 +41,10 @@
 import android.app.servertransaction.ActivityResultItem;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.PendingTransactionActions;
 import android.app.servertransaction.PendingTransactionActions.StopInfo;
+import android.app.servertransaction.ResumeActivityItem;
 import android.app.servertransaction.TransactionExecutor;
 import android.app.servertransaction.TransactionExecutorHelper;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -522,6 +524,8 @@
         boolean startsNotResumed;
         public final boolean isForward;
         int pendingConfigChanges;
+        // Whether we are in the process of performing on user leaving.
+        boolean mIsUserLeaving;
 
         Window mPendingRemoveWindow;
         WindowManager mPendingRemoveWindowManager;
@@ -3763,6 +3767,66 @@
         }
     }
 
+    @Override
+    public void handlePictureInPictureRequested(IBinder token) {
+        final ActivityClientRecord r = mActivities.get(token);
+        if (r == null) {
+            Log.w(TAG, "Activity to request PIP to no longer exists");
+            return;
+        }
+
+        r.activity.onPictureInPictureRequested();
+    }
+
+    /**
+     * Cycle activity through onPause and onUserLeaveHint so that PIP is entered if supported, then
+     * return to its previous state. This allows activities that rely on onUserLeaveHint instead of
+     * onPictureInPictureRequested to enter picture-in-picture.
+     */
+    public void schedulePauseAndReturnToCurrentState(IBinder token) {
+        final ActivityClientRecord r = mActivities.get(token);
+        if (r == null) {
+            Log.w(TAG, "Activity to request pause with user leaving hint to no longer exists");
+            return;
+        }
+
+        if (r.mIsUserLeaving) {
+            // The activity is about to perform user leaving, so there's no need to cycle ourselves.
+            return;
+        }
+
+        final int prevState = r.getLifecycleState();
+        if (prevState != ON_RESUME && prevState != ON_PAUSE) {
+            return;
+        }
+
+        switch (prevState) {
+            case ON_RESUME:
+                // Schedule a PAUSE then return to RESUME.
+                schedulePauseWithUserLeavingHint(r);
+                scheduleResume(r);
+                break;
+            case ON_PAUSE:
+                // Schedule a RESUME then return to PAUSE.
+                scheduleResume(r);
+                schedulePauseWithUserLeavingHint(r);
+                break;
+        }
+    }
+
+    private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) {
+        final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token);
+        transaction.setLifecycleStateRequest(PauseActivityItem.obtain(r.activity.isFinishing(),
+                /* userLeaving */ true, r.activity.mConfigChangeFlags, /* dontReport */ false));
+        executeTransaction(transaction);
+    }
+
+    private void scheduleResume(ActivityClientRecord r) {
+        final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token);
+        transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(/* isForward */ false));
+        executeTransaction(transaction);
+    }
+
     private void handleLocalVoiceInteractionStarted(IBinder token, IVoiceInteractor interactor) {
         final ActivityClientRecord r = mActivities.get(token);
         if (r != null) {
@@ -4483,6 +4547,7 @@
         if (r != null) {
             if (userLeaving) {
                 performUserLeavingActivity(r);
+                r.mIsUserLeaving = false;
             }
 
             r.activity.mConfigChangeFlags |= configChanges;
@@ -4497,6 +4562,8 @@
     }
 
     final void performUserLeavingActivity(ActivityClientRecord r) {
+        r.mIsUserLeaving = true;
+        mInstrumentation.callActivityOnPictureInPictureRequested(r.activity);
         mInstrumentation.callActivityOnUserLeaving(r.activity);
     }
 
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index d308adc..f9a689a 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -158,6 +158,9 @@
     public abstract void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
             Configuration overrideConfig);
 
+    /** 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);
 
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index df5d6c7..700b3c1 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -333,6 +333,7 @@
     boolean isInPictureInPictureMode(in IBinder token);
     boolean enterPictureInPictureMode(in IBinder token, in PictureInPictureParams params);
     void setPictureInPictureParams(in IBinder token, in PictureInPictureParams params);
+    void requestPictureInPictureMode(in IBinder token);
     int getMaxNumPictureInPictureActions(in IBinder token);
     IBinder getUriPermissionOwnerForActivity(in IBinder activityToken);
 
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 9e552e6..62c905d 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1519,6 +1519,16 @@
     public void callActivityOnUserLeaving(Activity activity) {
         activity.performUserLeaving();
     }
+
+    /**
+     * Perform calling of an activity's {@link Activity#onPictureInPictureRequested} method.
+     * The default implementation simply calls through to that method.
+     *
+     * @param activity The activity being notified that picture-in-picture is being requested.
+     */
+    public void callActivityOnPictureInPictureRequested(@NonNull Activity activity) {
+        activity.onPictureInPictureRequested();
+    }
     
     /*
      * Starts allocation counting. This triggers a gc and resets the counts.
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 69c37ec..31c73b9 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -50,6 +50,8 @@
 import android.content.Context;
 import android.content.IRestrictionsManager;
 import android.content.RestrictionsManager;
+import android.content.integrity.AppIntegrityManager;
+import android.content.integrity.IAppIntegrityManager;
 import android.content.om.IOverlayManager;
 import android.content.om.OverlayManager;
 import android.content.pm.CrossProfileApps;
@@ -160,6 +162,8 @@
 import android.permission.PermissionManager;
 import android.print.IPrintManager;
 import android.print.PrintManager;
+import android.security.FileIntegrityManager;
+import android.security.IFileIntegrityService;
 import android.service.oemlock.IOemLockService;
 import android.service.oemlock.OemLockManager;
 import android.service.persistentdata.IPersistentDataBlockService;
@@ -1212,6 +1216,7 @@
                         return new DynamicSystemManager(
                                 IDynamicSystemService.Stub.asInterface(b));
                     }});
+
         registerService(Context.BATTERY_STATS_SERVICE, BatteryStatsManager.class,
                 new CachedServiceFetcher<BatteryStatsManager>() {
                     @Override
@@ -1245,7 +1250,26 @@
                         return new IncrementalManager(
                                 IIncrementalManagerNative.Stub.asInterface(b));
                     }});
+
+        registerService(Context.FILE_INTEGRITY_SERVICE, FileIntegrityManager.class,
+                new CachedServiceFetcher<FileIntegrityManager>() {
+                    @Override
+                    public FileIntegrityManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getServiceOrThrow(
+                                Context.FILE_INTEGRITY_SERVICE);
+                        return new FileIntegrityManager(
+                                IFileIntegrityService.Stub.asInterface(b));
+                    }});
         //CHECKSTYLE:ON IndentationCheck
+        registerService(Context.APP_INTEGRITY_SERVICE, AppIntegrityManager.class,
+                new CachedServiceFetcher<AppIntegrityManager>() {
+                    @Override
+                    public AppIntegrityManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getServiceOrThrow(Context.APP_INTEGRITY_SERVICE);
+                        return new AppIntegrityManager(IAppIntegrityManager.Stub.asInterface(b));
+                    }});
 
         sInitializing = true;
         try {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3ca8f49..acdf919 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5061,12 +5061,17 @@
      *         owner. If Device ID attestation is requested (using {@link #ID_TYPE_SERIAL},
      *         {@link #ID_TYPE_IMEI} or {@link #ID_TYPE_MEID}), the caller must be the Device Owner
      *         or the Certificate Installer delegate.
-     * @throws IllegalArgumentException if the alias in {@code keySpec} is empty, if the
-     *         algorithm specification in {@code keySpec} is not {@code RSAKeyGenParameterSpec}
-     *         or {@code ECGenParameterSpec}, or if Device ID attestation was requested but the
-     *         {@code keySpec} does not contain an attestation challenge.
-     * @throws UnsupportedOperationException if Device ID attestation was requested but the
-     *         underlying hardware does not support it.
+     * @throws IllegalArgumentException in the following cases:
+     *         <p>
+     *         <ul>
+     *         <li>The alias in {@code keySpec} is empty.</li>
+     *         <li>The algorithm specification in {@code keySpec} is not
+     *         {@code RSAKeyGenParameterSpec} or {@code ECGenParameterSpec}.</li>
+     *         <li>Device ID attestation was requested but the {@code keySpec} does not contain an
+     *         attestation challenge.</li>
+     *         </ul>
+     * @throws UnsupportedOperationException if Device ID attestation or individual attestation
+     *         was requested but the underlying hardware does not support it.
      * @throws StrongBoxUnavailableException if the use of StrongBox for key generation was
      *         specified in {@code keySpec} but the device does not have one.
      * @see KeyGenParameterSpec.Builder#setAttestationChallenge(byte[])
@@ -6759,8 +6764,9 @@
      *
      * @hide
      */
-    public boolean checkDeviceIdentifierAccess(String packageName, int pid, int uid) {
-        throwIfParentInstance("checkDeviceIdentifierAccess");
+    @SystemApi
+    public boolean hasDeviceIdentifierAccess(@NonNull String packageName, int pid, int uid) {
+        throwIfParentInstance("hasDeviceIdentifierAccess");
         if (packageName == null) {
             return false;
         }
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 4d2e9a5..3d04437 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -77,8 +77,9 @@
 
     /** Get the list of callbacks. */
     @Nullable
+    @VisibleForTesting
     @UnsupportedAppUsage
-    List<ClientTransactionItem> getCallbacks() {
+    public List<ClientTransactionItem> getCallbacks() {
         return mActivityCallbacks;
     }
 
diff --git a/core/java/android/app/servertransaction/EnterPipRequestedItem.java b/core/java/android/app/servertransaction/EnterPipRequestedItem.java
new file mode 100644
index 0000000..b2a1276
--- /dev/null
+++ b/core/java/android/app/servertransaction/EnterPipRequestedItem.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.app.servertransaction;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+
+/**
+ * Request an activity to enter picture-in-picture mode.
+ * @hide
+ */
+public final class EnterPipRequestedItem extends ClientTransactionItem {
+
+    @Override
+    public void execute(ClientTransactionHandler client, IBinder token,
+            PendingTransactionActions pendingActions) {
+        client.handlePictureInPictureRequested(token);
+    }
+
+    // ObjectPoolItem implementation
+
+    private EnterPipRequestedItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static EnterPipRequestedItem obtain() {
+        EnterPipRequestedItem instance = ObjectPool.obtain(EnterPipRequestedItem.class);
+        if (instance == null) {
+            instance = new EnterPipRequestedItem();
+        }
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        ObjectPool.recycle(this);
+    }
+
+    // Parcelable implementation
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) { }
+
+    public static final @android.annotation.NonNull Creator<EnterPipRequestedItem> CREATOR =
+            new Creator<EnterPipRequestedItem>() {
+                public EnterPipRequestedItem createFromParcel(Parcel in) {
+                    return new EnterPipRequestedItem();
+                }
+
+                public EnterPipRequestedItem[] newArray(int size) {
+                    return new EnterPipRequestedItem[size];
+                }
+            };
+
+    @Override
+    public boolean equals(Object o) {
+        return this == o;
+    }
+
+    @Override
+    public String toString() {
+        return "EnterPipRequestedItem{}";
+    }
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 483bf3f..4815d78 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5045,6 +5045,14 @@
     public static final String INCREMENTAL_SERVICE = "incremental_service";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve an
+     * {@link android.security.FileIntegrityManager}.
+     * @see #getSystemService(String)
+     * @see android.security.FileIntegrityManager
+     */
+    public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/content/integrity/AppInstallMetadata.java b/core/java/android/content/integrity/AppInstallMetadata.java
index c963475..70776c7 100644
--- a/core/java/android/content/integrity/AppInstallMetadata.java
+++ b/core/java/android/content/integrity/AppInstallMetadata.java
@@ -86,6 +86,19 @@
         return mIsPreInstalled;
     }
 
+    @Override
+    public String toString() {
+        return String.format(
+                "AppInstallMetadata { PackageName = %s, AppCert = %s, InstallerName = %s,"
+                    + " InstallerCert = %s, VersionCode = %d, PreInstalled = %b }",
+                mPackageName,
+                mAppCertificate,
+                mInstallerName == null ? "null" : mInstallerName,
+                mInstallerCertificate == null ? "null" : mInstallerCertificate,
+                mVersionCode,
+                mIsPreInstalled);
+    }
+
     /** Builder class for constructing {@link AppInstallMetadata} objects. */
     public static final class Builder {
         private String mPackageName;
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 5b1f926..e5daaca 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -290,6 +290,34 @@
     public int colorMode = COLOR_MODE_DEFAULT;
 
     /**
+     * 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.
+     *
+     * <p>This setting should be used when low latency has a higher priority than image enhancement
+     * processing (e.g. for games or video conferencing).
+     *
+     * <p>If the Display sink is connected via HDMI, the device will begin to send infoframes with
+     * Auto Low Latency Mode enabled and Game Content Type. This will switch the connected display
+     * to a minimal image processing mode (if available), which reduces latency, improving the user
+     * experience for gaming or video conferencing applications. For more information, see HDMI 2.1
+     * specification.
+     *
+     * <p>If the Display sink has an internal connection or uses some other protocol than HDMI,
+     * effects may be similar but implementation-defined.
+     *
+     * <p>The ability to switch to a mode with minimal post proessing may be disabled by a user
+     * setting in the system settings menu. In that case, this field is ignored and the display will
+     * remain in its current mode.
+     *
+     * <p>Set from attribute {@link android.R.attr#preferMinimalPostProcessing}.
+     *
+     * @see android.view.WindowManager.LayoutParams#preferMinimalPostProcessing
+     * @see android.view.Display#isMinimalPostProcessingSupported
+     */
+    public boolean preferMinimalPostProcessing = false;
+
+    /**
      * Bit in {@link #flags} indicating whether this activity is able to
      * run in multiple processes.  If
      * true, the system may instantiate it in the some process as the
@@ -1004,6 +1032,7 @@
         requestedVrComponent = orig.requestedVrComponent;
         rotationAnimation = orig.rotationAnimation;
         colorMode = orig.colorMode;
+        preferMinimalPostProcessing = orig.preferMinimalPostProcessing;
         maxAspectRatio = orig.maxAspectRatio;
         minAspectRatio = orig.minAspectRatio;
     }
@@ -1231,6 +1260,7 @@
         dest.writeInt(colorMode);
         dest.writeFloat(maxAspectRatio);
         dest.writeFloat(minAspectRatio);
+        dest.writeBoolean(preferMinimalPostProcessing);
     }
 
     /**
@@ -1349,6 +1379,7 @@
         colorMode = source.readInt();
         maxAspectRatio = source.readFloat();
         minAspectRatio = source.readFloat();
+        preferMinimalPostProcessing = source.readBoolean();
     }
 
     /**
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index c955137..79a339f 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -24,7 +24,6 @@
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
-import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 
@@ -151,11 +150,15 @@
      * has a preference.
      * @param requestedModeId The preferred mode id for the top-most visible window that has a
      * preference.
+     * @param requestedMinimalPostProcessing The preferred minimal post processing setting for the
+     * display. This is true when there is at least one visible window that wants minimal post
+     * processng on.
      * @param inTraversal True if called from WindowManagerService during a window traversal
      * prior to call to performTraversalInTransactionFromWindowManager.
      */
     public abstract void setDisplayProperties(int displayId, boolean hasContent,
-            float requestedRefreshRate, int requestedModeId, boolean inTraversal);
+            float requestedRefreshRate, int requestedModeId, boolean requestedMinimalPostProcessing,
+            boolean inTraversal);
 
     /**
      * Applies an offset to the contents of a display, for example to avoid burn-in.
diff --git a/core/java/android/net/MatchAllNetworkSpecifier.java b/core/java/android/net/MatchAllNetworkSpecifier.java
index ab4f627..68a3935 100644
--- a/core/java/android/net/MatchAllNetworkSpecifier.java
+++ b/core/java/android/net/MatchAllNetworkSpecifier.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -27,10 +29,12 @@
  *
  * @hide
  */
+@SystemApi
 public final class MatchAllNetworkSpecifier extends NetworkSpecifier implements Parcelable {
     /**
      * Utility method which verifies that the ns argument is not a MatchAllNetworkSpecifier and
      * throws an IllegalArgumentException if it is.
+     * @hide
      */
     public static void checkNotMatchAllNetworkSpecifier(NetworkSpecifier ns) {
         if (ns instanceof MatchAllNetworkSpecifier) {
@@ -38,6 +42,7 @@
         }
     }
 
+    /** @hide */
     public boolean satisfiedBy(NetworkSpecifier other) {
         /*
          * The method is called by a NetworkRequest to see if it is satisfied by a proposed
@@ -64,11 +69,11 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         // Nothing to write.
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<MatchAllNetworkSpecifier> CREATOR =
+    public static final @NonNull Parcelable.Creator<MatchAllNetworkSpecifier> CREATOR =
             new Parcelable.Creator<MatchAllNetworkSpecifier>() {
         public MatchAllNetworkSpecifier createFromParcel(Parcel in) {
             return new MatchAllNetworkSpecifier();
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index ba4042d..39ddcb2 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -90,6 +90,15 @@
         throw new RemoteException();
     }
 
+    public void registerClientCallback(String name, IBinder service, IClientCallback cb)
+            throws RemoteException {
+        throw new RemoteException();
+    }
+
+    public void tryUnregisterService(String name, IBinder service) throws RemoteException {
+        throw new RemoteException();
+    }
+
     /**
      * Same as mServiceManager but used by apps.
      *
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 462627e..588bb18 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -494,25 +494,94 @@
      * Activity Action: Show setting page to process an Easy Connect (Wi-Fi DPP) QR code 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.
+     * 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.
      * <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 you safeguard against this by
+     * checking WifiManager.isEasyConnectSupported();
      * <p>
      * Input: The Intent's data URI specifies bootstrapping information for authenticating and
      * provisioning the peer, with the "DPP" scheme.
      * <p>
      * Output: After {@code startActivityForResult}, the callback {@code onActivityResult} will have
-     *         resultCode {@link android.app.Activity#RESULT_OK} if Wi-Fi Easy Connect configuration
-     *         success and the user clicks 'Done' button.
+     * 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}.
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_URI =
             "android.settings.PROCESS_WIFI_EASY_CONNECT_URI";
 
     /**
+     * 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},
+     * then this extra is not attached to the result intent.
+     */
+    public static final String EXTRA_EASY_CONNECT_ERROR_CODE =
+            "android.provider.extra.EASY_CONNECT_ERROR_CODE";
+
+    /**
+     * Activity Extra: The SSID that the Enrollee tried to connect to.
+     * <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 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}.
+     * 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.
+     */
+    public static final String EXTRA_EASY_CONNECT_ATTEMPTED_SSID =
+            "android.provider.extra.EASY_CONNECT_ATTEMPTED_SSID";
+
+    /**
+     * Activity Extra: The Channel List that the Enrollee used to scan a network.
+     * <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
+     * 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}.
+     * 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).
+     */
+    public static final String EXTRA_EASY_CONNECT_CHANNEL_LIST =
+            "android.provider.extra.EASY_CONNECT_CHANNEL_LIST";
+
+    /**
+     * 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.
+     */
+    public static final String EXTRA_EASY_CONNECT_BAND_LIST =
+            "android.provider.extra.EASY_CONNECT_BAND_LIST";
+
+    /**
      * Activity Action: Show settings to allow configuration of data and view data usage.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
diff --git a/core/java/android/security/FileIntegrityManager.java b/core/java/android/security/FileIntegrityManager.java
new file mode 100644
index 0000000..cdd6584
--- /dev/null
+++ b/core/java/android/security/FileIntegrityManager.java
@@ -0,0 +1,77 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+
+/**
+ * This class provides access to file integrity related operations.
+ */
+@SystemService(Context.FILE_INTEGRITY_SERVICE)
+public final class FileIntegrityManager {
+    @NonNull private final IFileIntegrityService mService;
+
+    /** @hide */
+    public FileIntegrityManager(@NonNull IFileIntegrityService service) {
+        mService = service;
+    }
+
+    /**
+     * Returns true if APK Verity is supported on the device. When supported, an APK can be
+     * installed with a fs-verity signature (if verified with trusted App Source Certificate) for
+     * continuous on-access verification.
+     */
+    public boolean isApkVeritySupported() {
+        try {
+            // Go through the service just to avoid exposing the vendor controlled system property
+            // to all apps.
+            return mService.isApkVeritySupported();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether the given certificate can be used to prove app's install source. Always
+     * return false if the feature is not supported.
+     *
+     * <p>A store can use this API to decide if a signature file needs to be downloaded. Also, if a
+     * store has shipped different certificates before (e.g. with stronger and weaker key), it can
+     * also use this API to download the best signature on the running device.
+     *
+     * @return whether the certificate is trusted in the system
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.INSTALL_PACKAGES,
+            android.Manifest.permission.REQUEST_INSTALL_PACKAGES
+    })
+    public boolean isAppSourceCertificateTrusted(@NonNull X509Certificate certificate)
+            throws CertificateEncodingException {
+        try {
+            return mService.isAppSourceCertificateTrusted(certificate.getEncoded());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/security/IFileIntegrityService.aidl b/core/java/android/security/IFileIntegrityService.aidl
new file mode 100644
index 0000000..ebb8bcb
--- /dev/null
+++ b/core/java/android/security/IFileIntegrityService.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+/**
+ * Binder interface to communicate with FileIntegrityService.
+ * @hide
+ */
+interface IFileIntegrityService {
+    boolean isApkVeritySupported();
+    boolean isAppSourceCertificateTrusted(in byte[] certificateBytes);
+}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index b555d20..4368115 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -21,6 +21,7 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.app.KeyguardManager;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -876,6 +877,31 @@
     }
 
     /**
+     * <p> Returns true if the connected display can be switched into a mode with minimal
+     * post processing. </p>
+     *
+     * <p> If the Display sink is connected via HDMI, this method will return true if the
+     * display supports either Auto Low Latency Mode or Game Content Type.
+     *
+     * <p> If the Display sink has an internal connection or uses some other protocol than
+     * HDMI, this method will return true if the sink can be switched into an
+     * implementation-defined low latency image processing mode. </p>
+     *
+     * <p> The ability to switch to a mode with minimal post processing may be disabled
+     * by a user setting in the system settings menu. In that case, this method returns
+     * false. </p>
+     *
+     * @see android.view.Window#setPreferMinimalPostProcessing
+     */
+    @SuppressLint("VisiblySynchronized")
+    public boolean isMinimalPostProcessingSupported() {
+        synchronized (this) {
+            updateDisplayInfoLocked();
+            return mDisplayInfo.minimalPostProcessingSupported;
+        }
+    }
+
+    /**
      * Request the display applies a color mode.
      * @hide
      */
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 8a5385d..7d455c9 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -184,6 +184,14 @@
     public Display.HdrCapabilities hdrCapabilities;
 
     /**
+     * Indicates whether the display can be switched into a mode with minimal post
+     * processing.
+     *
+     * @see android.view.Display#isMinimalPostProcessingSupported
+     */
+    public boolean minimalPostProcessingSupported;
+
+    /**
      * The logical display density which is the basis for density-independent
      * pixels.
      */
@@ -305,6 +313,7 @@
                 && colorMode == other.colorMode
                 && Arrays.equals(supportedColorModes, other.supportedColorModes)
                 && Objects.equals(hdrCapabilities, other.hdrCapabilities)
+                && minimalPostProcessingSupported == other.minimalPostProcessingSupported
                 && logicalDensityDpi == other.logicalDensityDpi
                 && physicalXDpi == other.physicalXDpi
                 && physicalYDpi == other.physicalYDpi
@@ -346,6 +355,7 @@
         supportedColorModes = Arrays.copyOf(
                 other.supportedColorModes, other.supportedColorModes.length);
         hdrCapabilities = other.hdrCapabilities;
+        minimalPostProcessingSupported = other.minimalPostProcessingSupported;
         logicalDensityDpi = other.logicalDensityDpi;
         physicalXDpi = other.physicalXDpi;
         physicalYDpi = other.physicalYDpi;
@@ -388,6 +398,7 @@
             supportedColorModes[i] = source.readInt();
         }
         hdrCapabilities = source.readParcelable(null);
+        minimalPostProcessingSupported = source.readBoolean();
         logicalDensityDpi = source.readInt();
         physicalXDpi = source.readFloat();
         physicalYDpi = source.readFloat();
@@ -430,6 +441,7 @@
             dest.writeInt(supportedColorModes[i]);
         }
         dest.writeParcelable(hdrCapabilities, flags);
+        dest.writeBoolean(minimalPostProcessingSupported);
         dest.writeInt(logicalDensityDpi);
         dest.writeFloat(physicalXDpi);
         dest.writeFloat(physicalYDpi);
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 5674de8..c67ff6e 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -25,6 +25,7 @@
 
 import dalvik.system.CloseGuard;
 
+import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
 
 /**
@@ -86,6 +87,7 @@
 
     /**
      * Disposes the receiver.
+     * Must be called on the same Looper thread to which the receiver is attached.
      */
     public void dispose() {
         dispose(false);
@@ -109,6 +111,7 @@
             mInputChannel = null;
         }
         mMessageQueue = null;
+        Reference.reachabilityFence(this);
     }
 
     /**
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index bcf5c78..7be3e6a 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -39,6 +39,17 @@
         mSurfacePosition = surfacePosition;
     }
 
+    public InsetsSourceControl(InsetsSourceControl other) {
+        mType = other.mType;
+        if (other.mLeash != null) {
+            mLeash = new SurfaceControl();
+            mLeash.copyFrom(other.mLeash);
+        } else {
+            mLeash = null;
+        }
+        mSurfacePosition = new Point(other.mSurfacePosition);
+    }
+
     public int getType() {
         return mType;
     }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index c566eae..f6d6522 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -169,6 +169,8 @@
     private static native int nativeGetActiveColorMode(IBinder displayToken);
     private static native boolean nativeSetActiveColorMode(IBinder displayToken,
             int colorMode);
+    private static native void nativeSetAutoLowLatencyMode(IBinder displayToken, boolean on);
+    private static native void nativeSetGameContentType(IBinder displayToken, boolean on);
     private static native void nativeSetDisplayPowerMode(
             IBinder displayToken, int mode);
     private static native void nativeDeferTransactionUntil(long transactionObj, long nativeObject,
@@ -186,6 +188,9 @@
 
     private static native Display.HdrCapabilities nativeGetHdrCapabilities(IBinder displayToken);
 
+    private static native boolean nativeGetAutoLowLatencyModeSupport(IBinder displayToken);
+    private static native boolean nativeGetGameContentTypeSupport(IBinder displayToken);
+
     private static native void nativeSetInputWindowInfo(long transactionObj, long nativeObject,
             InputWindowHandle handle);
 
@@ -441,10 +446,16 @@
     public static final int METADATA_TASK_ID = 3;
 
     /**
+     * The style of mouse cursor and hotspot.
+     * @hide
+     */
+    public static final int METADATA_MOUSE_CURSOR = 4;
+
+    /**
      * Accessibility ID to allow association between surfaces and accessibility tree.
      * @hide
      */
-    public static final int METADATA_ACCESSIBILITY_ID = 4;
+    public static final int METADATA_ACCESSIBILITY_ID = 5;
 
     /**
      * A wrapper around GraphicBuffer that contains extra information about how to
@@ -1683,6 +1694,28 @@
     /**
      * @hide
      */
+    public static void setAutoLowLatencyMode(IBinder displayToken, boolean on) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+
+        nativeSetAutoLowLatencyMode(displayToken, on);
+    }
+
+    /**
+     * @hide
+     */
+    public static void setGameContentType(IBinder displayToken, boolean on) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+
+        nativeSetGameContentType(displayToken, on);
+    }
+
+    /**
+     * @hide
+     */
     @UnsupportedAppUsage
     public static void setDisplayProjection(IBinder displayToken,
             int orientation, Rect layerStackRect, Rect displayRect) {
@@ -1734,6 +1767,28 @@
     /**
      * @hide
      */
+    public static boolean getAutoLowLatencyModeSupport(IBinder displayToken) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+
+        return nativeGetAutoLowLatencyModeSupport(displayToken);
+    }
+
+    /**
+     * @hide
+     */
+    public static boolean getGameContentTypeSupport(IBinder displayToken) {
+        if (displayToken == null) {
+            throw new IllegalArgumentException("displayToken must not be null");
+        }
+
+        return nativeGetGameContentTypeSupport(displayToken);
+    }
+
+    /**
+     * @hide
+     */
     @UnsupportedAppUsage
     public static IBinder createDisplay(String name, boolean secure) {
         if (name == null) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3bab9eb..9fd3464 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -7619,11 +7619,22 @@
     }
 
     private void dispatchInsetsChanged(InsetsState insetsState) {
+        if (Binder.getCallingPid() == android.os.Process.myPid()) {
+            insetsState = new InsetsState(insetsState, true /* copySource */);
+        }
         mHandler.obtainMessage(MSG_INSETS_CHANGED, insetsState).sendToTarget();
     }
 
     private void dispatchInsetsControlChanged(InsetsState insetsState,
             InsetsSourceControl[] activeControls) {
+        if (Binder.getCallingPid() == android.os.Process.myPid()) {
+            insetsState = new InsetsState(insetsState, true /* copySource */);
+            if (activeControls != null) {
+                for (int i = activeControls.length - 1; i >= 0; i--) {
+                    activeControls[i] = new InsetsSourceControl(activeControls[i]);
+                }
+            }
+        }
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = insetsState;
         args.arg2 = activeControls;
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 0776469..a1894f3 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1199,6 +1199,44 @@
     }
 
     /**
+     * If {@code isPreferred} is true, this method requests that the connected display does minimal
+     * post processing when this window is visible on the screen. Otherwise, it requests that the
+     * display switches back to standard image processing.
+     *
+     * <p> By default, the display does not do minimal post processing and if this is desired, this
+     * method should not be used. It should be used with {@code isPreferred=true} when low
+     * latency has a higher priority than image enhancement processing (e.g. for games or video
+     * conferencing). The display will automatically go back into standard image processing mode
+     * when no window requesting minimal posst processing is visible on screen anymore.
+     * {@code setPreferMinimalPostProcessing(false)} can be used if
+     * {@code setPreferMinimalPostProcessing(true)} was previously called for this window and
+     * minimal post processing is no longer required.
+     *
+     * <p>If the Display sink is connected via HDMI, the device will begin to send infoframes with
+     * Auto Low Latency Mode enabled and Game Content Type. This will switch the connected display
+     * to a minimal image processing mode (if available), which reduces latency, improving the user
+     * experience for gaming or video conferencing applications. For more information, see HDMI 2.1
+     * specification.
+     *
+     * <p>If the Display sink has an internal connection or uses some other protocol than HDMI,
+     * effects may be similar but implementation-defined.
+     *
+     * <p>The ability to switch to a mode with minimal post proessing may be disabled by a user
+     * setting in the system settings menu. In that case, this method does nothing.
+     *
+     * @see android.content.pm.ActivityInfo#preferMinimalPostProcessing
+     * @see android.view.Display#isMinimalPostProcessingSupported
+     * @see android.view.WindowManager.LayoutParams#preferMinimalPostProcessing
+     *
+     * @param isPreferred Indicates whether minimal post processing is preferred for this window
+     *                    ({@code isPreferred=true}) or not ({@code isPreferred=false}).
+     */
+    public void setPreferMinimalPostProcessing(boolean isPreferred) {
+        mWindowAttributes.preferMinimalPostProcessing = isPreferred;
+        dispatchWindowAttributesChanged(mWindowAttributes);
+    }
+
+    /**
      * Returns the requested color mode of the window, one of
      * {@link ActivityInfo#COLOR_MODE_DEFAULT}, {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT}
      * or {@link ActivityInfo#COLOR_MODE_HDR}. If {@link ActivityInfo#COLOR_MODE_WIDE_COLOR_GAMUT}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index fc12639..f7d9706 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2607,6 +2607,33 @@
         public long hideTimeoutMilliseconds = -1;
 
         /**
+         * Indicates whether this window wants the connected display to do minimal post processing
+         * on the produced image or video frames. This will only be requested if the window is
+         * visible on the screen.
+         *
+         * <p>This setting should be used when low latency has a higher priority than image
+         * enhancement processing (e.g. for games or video conferencing).
+         *
+         * <p>If the Display sink is connected via HDMI, the device will begin to send infoframes
+         * with Auto Low Latency Mode enabled and Game Content Type. This will switch the connected
+         * display to a minimal image processing mode (if available), which reduces latency,
+         * improving the user experience for gaming or video conferencing applications. For more
+         * information, see HDMI 2.1 specification.
+         *
+         * <p>If the Display sink has an internal connection or uses some other protocol than HDMI,
+         * effects may be similar but implementation-defined.
+         *
+         * <p>The ability to switch to a mode with minimal post proessing may be disabled by a user
+         * setting in the system settings menu. In that case, this field is ignored and the display
+         * will remain in its current mode.
+         *
+         * @see android.content.pm.ActivityInfo#preferMinimalPostProcessing
+         * @see android.view.Display#isMinimalPostProcessingSupported
+         * @see android.view.Window#setPreferMinimalPostProcessing
+         */
+        public boolean preferMinimalPostProcessing = false;
+
+        /**
          * The color mode requested by this window. The target display may
          * not be able to honor the request. When the color mode is not set
          * to {@link ActivityInfo#COLOR_MODE_DEFAULT}, it might override the
@@ -2909,6 +2936,7 @@
             out.writeInt(mFitWindowInsetsTypes);
             out.writeInt(mFitWindowInsetsSides);
             out.writeBoolean(mFitIgnoreVisibility);
+            out.writeBoolean(preferMinimalPostProcessing);
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR
@@ -2969,6 +2997,7 @@
             mFitWindowInsetsTypes = in.readInt();
             mFitWindowInsetsSides = in.readInt();
             mFitIgnoreVisibility = in.readBoolean();
+            preferMinimalPostProcessing = in.readBoolean();
         }
 
         @SuppressWarnings({"PointlessBitwiseExpression"})
@@ -3014,6 +3043,8 @@
         public static final int COLOR_MODE_CHANGED = 1 << 26;
         /** {@hide} */
         public static final int INSET_FLAGS_CHANGED = 1 << 27;
+        /** {@hide} */
+        public static final int MINIMAL_POST_PROCESSING_PREFERENCE_CHANGED = 1 << 28;
 
         // internal buffer to backup/restore parameters under compatibility mode.
         private int[] mCompatibilityParamsBackup = null;
@@ -3194,6 +3225,11 @@
                 changes |= COLOR_MODE_CHANGED;
             }
 
+            if (preferMinimalPostProcessing != o.preferMinimalPostProcessing) {
+                preferMinimalPostProcessing = o.preferMinimalPostProcessing;
+                changes |= MINIMAL_POST_PROCESSING_PREFERENCE_CHANGED;
+            }
+
             // This can't change, it's only set at window creation time.
             hideTimeoutMilliseconds = o.hideTimeoutMilliseconds;
 
@@ -3348,6 +3384,10 @@
             if (mColorMode != COLOR_MODE_DEFAULT) {
                 sb.append(" colorMode=").append(ActivityInfo.colorModeToString(mColorMode));
             }
+            if (preferMinimalPostProcessing) {
+                sb.append(" preferMinimalPostProcessing=");
+                sb.append(preferMinimalPostProcessing);
+            }
             sb.append(System.lineSeparator());
             sb.append(prefix).append("  fl=").append(
                     ViewDebug.flagsToString(LayoutParams.class, "flags", flags));
diff --git a/core/java/android/view/inline/InlinePresentationSpec.java b/core/java/android/view/inline/InlinePresentationSpec.java
index 1eddef5..a85b5f1 100644
--- a/core/java/android/view/inline/InlinePresentationSpec.java
+++ b/core/java/android/view/inline/InlinePresentationSpec.java
@@ -31,6 +31,7 @@
  */
 @DataClass(genEqualsHashCode = true, genToString = true, genBuilder = true)
 public final class InlinePresentationSpec implements Parcelable {
+
     /** The minimal size of the suggestion. */
     @NonNull
     private final Size mMinSize;
@@ -38,7 +39,26 @@
     @NonNull
     private final Size mMaxSize;
 
-    // TODO(b/137800469): add more attributes, such as text appearance info.
+    /**
+     * The fully qualified resource name of the UI style resource identifier, defaults to {@code
+     * null}.
+     *
+     * <p> The value can be obtained by calling {@code Resources#getResourceName(int)}.
+     */
+    @Nullable
+    private final String mStyle;
+
+    private static String defaultStyle() {
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    public @Nullable String getStyle() {
+        return mStyle;
+    }
+
 
     /** @hide */
     @DataClass.Suppress({"setMaxSize", "setMinSize"})
@@ -63,13 +83,15 @@
     @DataClass.Generated.Member
     /* package-private */ InlinePresentationSpec(
             @NonNull Size minSize,
-            @NonNull Size maxSize) {
+            @NonNull Size maxSize,
+            @Nullable String style) {
         this.mMinSize = minSize;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mMinSize);
         this.mMaxSize = maxSize;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mMaxSize);
+        this.mStyle = style;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -98,7 +120,8 @@
 
         return "InlinePresentationSpec { " +
                 "minSize = " + mMinSize + ", " +
-                "maxSize = " + mMaxSize +
+                "maxSize = " + mMaxSize + ", " +
+                "style = " + mStyle +
         " }";
     }
 
@@ -116,7 +139,8 @@
         //noinspection PointlessBooleanExpression
         return true
                 && java.util.Objects.equals(mMinSize, that.mMinSize)
-                && java.util.Objects.equals(mMaxSize, that.mMaxSize);
+                && java.util.Objects.equals(mMaxSize, that.mMaxSize)
+                && java.util.Objects.equals(mStyle, that.mStyle);
     }
 
     @Override
@@ -128,6 +152,7 @@
         int _hash = 1;
         _hash = 31 * _hash + java.util.Objects.hashCode(mMinSize);
         _hash = 31 * _hash + java.util.Objects.hashCode(mMaxSize);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mStyle);
         return _hash;
     }
 
@@ -137,8 +162,12 @@
         // You can override field parcelling by defining methods like:
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
+        byte flg = 0;
+        if (mStyle != null) flg |= 0x4;
+        dest.writeByte(flg);
         dest.writeSize(mMinSize);
         dest.writeSize(mMaxSize);
+        if (mStyle != null) dest.writeString(mStyle);
     }
 
     @Override
@@ -152,8 +181,10 @@
         // You can override field unparcelling by defining methods like:
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
+        byte flg = in.readByte();
         Size minSize = (Size) in.readSize();
         Size maxSize = (Size) in.readSize();
+        String style = (flg & 0x4) == 0 ? null : in.readString();
 
         this.mMinSize = minSize;
         com.android.internal.util.AnnotationValidations.validate(
@@ -161,6 +192,7 @@
         this.mMaxSize = maxSize;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mMaxSize);
+        this.mStyle = style;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -188,6 +220,7 @@
 
         private @NonNull Size mMinSize;
         private @NonNull Size mMaxSize;
+        private @Nullable String mStyle;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -210,19 +243,37 @@
                     NonNull.class, null, mMaxSize);
         }
 
+        /**
+         * The fully qualified resource name of the UI style resource identifier, defaults to {@code
+         * null}.
+         *
+         * <p> The value can be obtained by calling {@code Resources#getResourceName(int)}.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setStyle(@Nullable String value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4;
+            mStyle = value;
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull InlinePresentationSpec build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x4; // Mark builder used
+            mBuilderFieldsSet |= 0x8; // Mark builder used
 
+            if ((mBuilderFieldsSet & 0x4) == 0) {
+                mStyle = defaultStyle();
+            }
             InlinePresentationSpec o = new InlinePresentationSpec(
                     mMinSize,
-                    mMaxSize);
+                    mMaxSize,
+                    mStyle);
             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");
             }
@@ -230,10 +281,10 @@
     }
 
     @DataClass.Generated(
-            time = 1574406062532L,
+            time = 1577145109444L,
             codegenVersion = "1.0.14",
             sourceFile = "frameworks/base/core/java/android/view/inline/InlinePresentationSpec.java",
-            inputSignatures = "private final @android.annotation.NonNull android.util.Size mMinSize\nprivate final @android.annotation.NonNull android.util.Size mMaxSize\nclass InlinePresentationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "private final @android.annotation.NonNull android.util.Size mMinSize\nprivate final @android.annotation.NonNull android.util.Size mMaxSize\nprivate final @android.annotation.Nullable java.lang.String mStyle\nprivate static  java.lang.String defaultStyle()\npublic @android.annotation.Nullable java.lang.String getStyle()\nclass InlinePresentationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index dd609ee..386c9cb 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -36,7 +36,17 @@
     /** Constant used to indicate not putting a cap on the number of suggestions to return. */
     public static final int SUGGESTION_COUNT_UNLIMITED = Integer.MAX_VALUE;
 
+    /**
+     * Max number of suggestions expected from the response. Defaults to {@code
+     * SUGGESTION_COUNT_UNLIMITED} if not set.
+     */
     private final int mMaxSuggestionCount;
+
+    /**
+     * The {@link InlinePresentationSpec} for each suggestion in the response. If the max suggestion
+     * count is larger than the number of specs in the list, then the last spec is used for the
+     * remainder of the suggestions.
+     */
     private final @NonNull List<InlinePresentationSpec> mPresentationSpecs;
 
     private void onConstructed() {
@@ -79,11 +89,20 @@
         onConstructed();
     }
 
+    /**
+     * Max number of suggestions expected from the response. Defaults to {@code
+     * SUGGESTION_COUNT_UNLIMITED} if not set.
+     */
     @DataClass.Generated.Member
     public int getMaxSuggestionCount() {
         return mMaxSuggestionCount;
     }
 
+    /**
+     * The {@link InlinePresentationSpec} for each suggestion in the response. If the max suggestion
+     * count is larger than the number of specs in the list, then the last spec is used for the
+     * remainder of the suggestions.
+     */
     @DataClass.Generated.Member
     public @NonNull List<InlinePresentationSpec> getPresentationSpecs() {
         return mPresentationSpecs;
@@ -189,6 +208,14 @@
 
         private long mBuilderFieldsSet = 0L;
 
+        /**
+         * Creates a new Builder.
+         *
+         * @param presentationSpecs
+         *   The {@link InlinePresentationSpec} for each suggestion in the response. If the max suggestion
+         *   count is larger than the number of specs in the list, then the last spec is used for the
+         *   remainder of the suggestions.
+         */
         public Builder(
                 @NonNull List<InlinePresentationSpec> presentationSpecs) {
             mPresentationSpecs = presentationSpecs;
@@ -196,6 +223,10 @@
                     NonNull.class, null, mPresentationSpecs);
         }
 
+        /**
+         * Max number of suggestions expected from the response. Defaults to {@code
+         * SUGGESTION_COUNT_UNLIMITED} if not set.
+         */
         @DataClass.Generated.Member
         public @NonNull Builder setMaxSuggestionCount(int value) {
             checkNotUsed();
@@ -204,6 +235,11 @@
             return this;
         }
 
+        /**
+         * The {@link InlinePresentationSpec} for each suggestion in the response. If the max suggestion
+         * count is larger than the number of specs in the list, then the last spec is used for the
+         * remainder of the suggestions.
+         */
         @DataClass.Generated.Member
         @Override
         @NonNull Builder setPresentationSpecs(@NonNull List<InlinePresentationSpec> value) {
@@ -247,7 +283,7 @@
     }
 
     @DataClass.Generated(
-            time = 1574406255024L,
+            time = 1576637222199L,
             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 []")
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index 237d1a9..1aa2aec 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -42,6 +42,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -110,7 +111,6 @@
 
     /**
      * Returns the text that was used to generate these links.
-     * @hide
      */
     @NonNull
     public String getText() {
@@ -342,6 +342,7 @@
         private final boolean mLegacyFallback;
         @Nullable private String mCallingPackageName;
         private final Bundle mExtras;
+        @Nullable private final ZonedDateTime mReferenceTime;
         @UserIdInt
         private int mUserId = UserHandle.USER_NULL;
 
@@ -350,11 +351,13 @@
                 LocaleList defaultLocales,
                 EntityConfig entityConfig,
                 boolean legacyFallback,
+                ZonedDateTime referenceTime,
                 Bundle extras) {
             mText = text;
             mDefaultLocales = defaultLocales;
             mEntityConfig = entityConfig;
             mLegacyFallback = legacyFallback;
+            mReferenceTime = referenceTime;
             mExtras = extras;
         }
 
@@ -395,6 +398,15 @@
         }
 
         /**
+         * @return reference time based on which relative dates (e.g. "tomorrow") should be
+         *      interpreted.
+         */
+        @Nullable
+        public ZonedDateTime getReferenceTime() {
+            return mReferenceTime;
+        }
+
+        /**
          * Sets the name of the package that is sending this request.
          * <p>
          * Package-private for SystemTextClassifier's use.
@@ -454,6 +466,7 @@
             @Nullable private EntityConfig mEntityConfig;
             private boolean mLegacyFallback = true; // Use legacy fall back by default.
             @Nullable private Bundle mExtras;
+            @Nullable private ZonedDateTime mReferenceTime;
 
             public Builder(@NonNull CharSequence text) {
                 mText = Objects.requireNonNull(text);
@@ -511,13 +524,26 @@
             }
 
             /**
+             * @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.
+             *
+             * @return this builder
+             */
+            @NonNull
+            public Builder setReferenceTime(@Nullable ZonedDateTime referenceTime) {
+                mReferenceTime = referenceTime;
+                return this;
+            }
+
+            /**
              * Builds and returns the request object.
              */
             @NonNull
             public Request build() {
                 return new Request(
                         mText, mDefaultLocales, mEntityConfig,
-                        mLegacyFallback,
+                        mLegacyFallback, mReferenceTime,
                         mExtras == null ? Bundle.EMPTY : mExtras);
             }
         }
@@ -535,6 +561,7 @@
             dest.writeString(mCallingPackageName);
             dest.writeInt(mUserId);
             dest.writeBundle(mExtras);
+            dest.writeString(mReferenceTime == null ? null : mReferenceTime.toString());
         }
 
         private static Request readFromParcel(Parcel in) {
@@ -544,9 +571,12 @@
             final String callingPackageName = in.readString();
             final int userId = in.readInt();
             final Bundle extras = in.readBundle();
+            final String referenceTimeString = in.readString();
+            final ZonedDateTime referenceTime = referenceTimeString == null
+                    ? null : ZonedDateTime.parse(referenceTimeString);
 
             final Request request = new Request(text, defaultLocales, entityConfig,
-                    /* legacyFallback= */ true, extras);
+                    /* legacyFallback= */ true, referenceTime, extras);
             request.setCallingPackageName(callingPackageName);
             request.setUserId(userId);
             return request;
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 392e67e..a86b566 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -1439,20 +1439,22 @@
 
         final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
 
-        // Stacked apps get a disambiguation first
         if (targetInfo instanceof MultiDisplayResolveInfo) {
             MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo;
-            CharSequence[] labels = new CharSequence[mti.getTargets().size()];
-            int i = 0;
-            for (TargetInfo ti : mti.getTargets()) {
-                labels[i++] = ti.getResolveInfo().loadLabel(getPackageManager());
-            }
-            ChooserStackedAppDialogFragment f = new ChooserStackedAppDialogFragment(
-                    targetInfo.getDisplayLabel(),
-                    ((MultiDisplayResolveInfo) targetInfo).getTargets(), labels);
+            if (!mti.hasSelected()) {
+                // Stacked apps get a disambiguation first
+                CharSequence[] labels = new CharSequence[mti.getTargets().size()];
+                int i = 0;
+                for (TargetInfo ti : mti.getTargets()) {
+                    labels[i++] = ti.getResolveInfo().loadLabel(getPackageManager());
+                }
+                ChooserStackedAppDialogFragment f = new ChooserStackedAppDialogFragment(
+                        targetInfo.getDisplayLabel(),
+                        ((MultiDisplayResolveInfo) targetInfo), labels, which);
 
-            f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
-            return;
+                f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
+                return;
+            }
         }
 
         super.startSelected(which, always, filtered);
diff --git a/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java b/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java
index ff6582d..f4c69a5 100644
--- a/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java
+++ b/core/java/com/android/internal/app/ChooserStackedAppDialogFragment.java
@@ -24,10 +24,7 @@
 import android.content.res.Configuration;
 import android.os.Bundle;
 
-import com.android.internal.app.chooser.DisplayResolveInfo;
-
-import java.util.ArrayList;
-import java.util.List;
+import com.android.internal.app.chooser.MultiDisplayResolveInfo;
 
 /**
  * Shows individual actions for a "stacked" app target - such as an app with multiple posting
@@ -38,24 +35,20 @@
     private static final String TITLE_KEY = "title";
     private static final String PINNED_KEY = "pinned";
 
-    private List<DisplayResolveInfo> mTargetInfos = new ArrayList<>();
+    private MultiDisplayResolveInfo mTargetInfos;
     private CharSequence[] mLabels;
+    private int mParentWhich;
 
     public ChooserStackedAppDialogFragment() {
     }
 
-    public ChooserStackedAppDialogFragment(CharSequence title) {
-        Bundle args = new Bundle();
-        args.putCharSequence(TITLE_KEY, title);
-        setArguments(args);
-    }
-
     public ChooserStackedAppDialogFragment(CharSequence title,
-            List<DisplayResolveInfo> targets, CharSequence[] labels) {
+            MultiDisplayResolveInfo targets, CharSequence[] labels, int parentWhich) {
         Bundle args = new Bundle();
         args.putCharSequence(TITLE_KEY, title);
         mTargetInfos = targets;
         mLabels = labels;
+        mParentWhich = parentWhich;
         setArguments(args);
     }
 
@@ -72,7 +65,8 @@
     @Override
     public void onClick(DialogInterface dialog, int which) {
         final Bundle args = getArguments();
-        mTargetInfos.get(which).start(getActivity(), null);
+        mTargetInfos.setSelected(which);
+        ((ChooserActivity) getActivity()).startSelected(mParentWhich, false, true);
         dismiss();
     }
 
diff --git a/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java
index 55200c8..e582583 100644
--- a/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java
@@ -16,6 +16,12 @@
 
 package com.android.internal.app.chooser;
 
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import com.android.internal.app.ResolverActivity;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -27,6 +33,8 @@
     List<DisplayResolveInfo> mTargetInfos = new ArrayList<>();
     // We'll use this DRI for basic presentation info - eg icon, name.
     final DisplayResolveInfo mBaseInfo;
+    // Index of selected target
+    private int mSelected = -1;
 
     /**
      * @param firstInfo A representative DRI to use for the main icon, title, etc for this Info.
@@ -57,4 +65,30 @@
         return mTargetInfos;
     }
 
+    public void setSelected(int selected) {
+        mSelected = selected;
+    }
+
+    /**
+     * Whether or not the user has selected a specific target for this MultiInfo.
+     */
+    public boolean hasSelected() {
+        return mSelected >= 0;
+    }
+
+    @Override
+    public boolean start(Activity activity, Bundle options) {
+        return mTargetInfos.get(mSelected).start(activity, options);
+    }
+
+    @Override
+    public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
+        return mTargetInfos.get(mSelected).startAsCaller(activity, options, userId);
+    }
+
+    @Override
+    public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
+        return mTargetInfos.get(mSelected).startAsUser(activity, options, user);
+    }
+
 }
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index dec9ae7..221cd6d 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -66,6 +66,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.regex.Pattern;
 
 /**
  * A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local
@@ -388,7 +389,9 @@
                 resolveProjection(projection), parentDocumentId, parent);
         if (parent.isDirectory()) {
             for (File file : FileUtils.listFilesOrEmpty(parent)) {
-                includeFile(result, null, file);
+                if (!shouldHide(file)) {
+                    includeFile(result, null, file);
+                }
             }
         } else {
             Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory");
@@ -422,6 +425,8 @@
         pending.add(folder);
         while (!pending.isEmpty() && result.getCount() < 24) {
             final File file = pending.removeFirst();
+            if (shouldHide(file)) continue;
+
             if (file.isDirectory()) {
                 for (File child : file.listFiles()) {
                     pending.add(child);
@@ -540,6 +545,7 @@
         } else {
             file = getFileForDocId(docId);
         }
+
         final String mimeType = getDocumentType(docId, file);
         row.add(Document.COLUMN_DOCUMENT_ID, docId);
         row.add(Document.COLUMN_MIME_TYPE, mimeType);
@@ -598,6 +604,17 @@
         return row;
     }
 
+    private static final Pattern PATTERN_HIDDEN_PATH = Pattern.compile(
+            "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb|sandbox)$");
+
+    /**
+     * In a scoped storage world, access to "Android/data" style directories are
+     * hidden for privacy reasons.
+     */
+    protected boolean shouldHide(@NonNull File file) {
+        return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches());
+    }
+
     protected boolean shouldBlockFromTree(@NonNull String docId) {
         return false;
     }
diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h
index 99b5f85..a3c455b 100644
--- a/core/jni/android_media_AudioFormat.h
+++ b/core/jni/android_media_AudioFormat.h
@@ -38,6 +38,7 @@
 #define ENCODING_AC4            17
 #define ENCODING_E_AC3_JOC      18
 #define ENCODING_DOLBY_MAT      19
+#define ENCODING_OPUS           20
 
 #define ENCODING_INVALID    0
 #define ENCODING_DEFAULT    1
@@ -88,6 +89,8 @@
         return AUDIO_FORMAT_DEFAULT;
     case ENCODING_DOLBY_MAT:
         return AUDIO_FORMAT_MAT;
+    case ENCODING_OPUS:
+        return AUDIO_FORMAT_OPUS;
     default:
         return AUDIO_FORMAT_INVALID;
     }
@@ -142,6 +145,8 @@
     case AUDIO_FORMAT_MAT_2_0:
     case AUDIO_FORMAT_MAT_2_1:
         return ENCODING_DOLBY_MAT;
+    case AUDIO_FORMAT_OPUS:
+        return ENCODING_OPUS;
     case AUDIO_FORMAT_DEFAULT:
         return ENCODING_DEFAULT;
     default:
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 33da34d..4a7276c 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1174,6 +1174,34 @@
             capabilities.getDesiredMaxAverageLuminance(), capabilities.getDesiredMinLuminance());
 }
 
+static jboolean nativeGetAutoLowLatencyModeSupport(JNIEnv* env, jclass clazz, jobject tokenObject) {
+    sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
+    if (token == NULL) return NULL;
+
+    return SurfaceComposerClient::getAutoLowLatencyModeSupport(token);
+}
+
+static jboolean nativeGetGameContentTypeSupport(JNIEnv* env, jclass clazz, jobject tokenObject) {
+    sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
+    if (token == NULL) return NULL;
+
+    return SurfaceComposerClient::getGameContentTypeSupport(token);
+}
+
+static void nativeSetAutoLowLatencyMode(JNIEnv* env, jclass clazz, jobject tokenObject, jboolean on) {
+    sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
+    if (token == NULL) return;
+
+    SurfaceComposerClient::setAutoLowLatencyMode(token, on);
+}
+
+static void nativeSetGameContentType(JNIEnv* env, jclass clazz, jobject tokenObject, jboolean on) {
+    sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
+    if (token == NULL) return;
+
+    SurfaceComposerClient::setGameContentType(token, on);
+}
+
 static jlong nativeReadFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
     Parcel* parcel = parcelForJavaObject(env, parcelObj);
     if (parcel == NULL) {
@@ -1376,6 +1404,14 @@
             (void*)nativeGetActiveColorMode},
     {"nativeSetActiveColorMode", "(Landroid/os/IBinder;I)Z",
             (void*)nativeSetActiveColorMode},
+    {"nativeGetAutoLowLatencyModeSupport", "(Landroid/os/IBinder;)Z",
+            (void*)nativeGetAutoLowLatencyModeSupport },
+    {"nativeSetAutoLowLatencyMode", "(Landroid/os/IBinder;Z)V",
+            (void*)nativeSetAutoLowLatencyMode },
+    {"nativeGetGameContentTypeSupport", "(Landroid/os/IBinder;)Z",
+            (void*)nativeGetGameContentTypeSupport },
+    {"nativeSetGameContentType", "(Landroid/os/IBinder;Z)V",
+            (void*)nativeSetGameContentType },
     {"nativeGetCompositionDataspaces", "()[I",
             (void*)nativeGetCompositionDataspaces},
     {"nativeGetHdrCapabilities", "(Landroid/os/IBinder;)Landroid/view/Display$HdrCapabilities;",
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index b2a19cf..97c94ec 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -716,7 +716,7 @@
     // OS: 6.0
     ACCESSIBILITY = 2;
 
-    // OPEN: Settings > Accessibility > Captions
+    // OPEN: Settings > Accessibility > Captions preference
     // CATEGORY: SETTINGS
     // OS: 6.0
     ACCESSIBILITY_CAPTION_PROPERTIES = 3;
@@ -2517,4 +2517,24 @@
     // OS: R
     DIALOG_MAGNIFICATION_CAPABILITY = 1816;
 
+    // OPEN: Settings > Accessibility > Color inversion
+    // CATEGORY: SETTINGS
+    // OS: R
+    ACCESSIBILITY_COLOR_INVERSION_SETTINGS = 1817;
+
+    // OPEN: Settings > Accessibility > Color inversion > Edit shortcut dialog
+    // CATEGORY: SETTINGS
+    // OS: R
+    DIALOG_COLOR_INVERSION_EDIT_SHORTCUT = 1818;
+
+    // OPEN: Settings > Accessibility > Captions preference > Captions appearance
+    // CATEGORY: SETTINGS
+    // OS: R
+    ACCESSIBILITY_CAPTION_APPEARANCE = 1819;
+
+    // OPEN: Settings > Accessibility > Captions preference > More options
+    // CATEGORY: SETTINGS
+    // OS: R
+    ACCESSIBILITY_CAPTION_MORE_OPTIONS = 1820;
+
 }
diff --git a/core/proto/android/util/quotatracker.proto b/core/proto/android/util/quotatracker.proto
index 0dea853..5d022ed 100644
--- a/core/proto/android/util/quotatracker.proto
+++ b/core/proto/android/util/quotatracker.proto
@@ -34,17 +34,25 @@
   // Current elapsed realtime.
   optional int64 elapsed_realtime = 3;
 
-  message AlarmListener {
+  message InQuotaAlarmListener {
     option (.android.msg_privacy).dest = DEST_AUTOMATIC;
 
-    // Whether the listener is waiting for an alarm or not.
-    optional bool is_waiting = 1;
-    // The time at which the alarm should go off, in the elapsed realtime timebase. Only
-    // valid if is_waiting is true.
-    optional int64 trigger_time_elapsed = 2;
-  }
+    // The time at which the alarm is set to go off, in the elapsed realtime timebase.
+    optional int64 trigger_time_elapsed = 1;
 
-  // Next tag: 4
+    message Alarm {
+      option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+      optional UptcProto uptc = 1;
+
+      // The time at which the UPTC will be in quota, in the elapsed realtime timebase.
+      optional int64 in_quota_time_elapsed = 2;
+    }
+    repeated Alarm alarms = 2;
+  }
+  optional InQuotaAlarmListener in_quota_alarm_listener = 4;
+
+  // Next tag: 5
 }
 
 // A com.android.util.quota.Category object.
@@ -118,8 +126,6 @@
     repeated Event events = 3;
 
     repeated ExecutionStats execution_stats = 4;
-
-    optional QuotaTrackerProto.AlarmListener in_quota_alarm_listener = 5;
   }
   repeated UptcStats uptc_stats = 3;
 
@@ -195,8 +201,6 @@
     repeated TimingSession saved_sessions = 4;
 
     repeated ExecutionStats execution_stats = 5;
-
-    optional QuotaTrackerProto.AlarmListener in_quota_alarm_listener = 6;
   }
   repeated UptcStats uptc_stats = 3;
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 073ab5a..7719165 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -347,6 +347,10 @@
     <protected-broadcast android:name="com.android.server.am.DELETE_DUMPHEAP" />
     <protected-broadcast android:name="com.android.server.net.action.SNOOZE_WARNING" />
     <protected-broadcast android:name="com.android.server.net.action.SNOOZE_RAPID" />
+    <protected-broadcast android:name="com.android.server.wifi.ACTION_SHOW_SET_RANDOMIZATION_DETAILS" />
+    <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_APP" />
+    <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_APP" />
+    <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISMISSED" />
     <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION" />
     <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK" />
     <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK" />
@@ -722,6 +726,11 @@
     <!-- ====================================================================== -->
     <eat-comment />
 
+    <!-- @SystemApi Allows accessing the messages on ICC
+         @hide Used internally. -->
+    <permission android:name="android.permission.ACCESS_MESSAGES_ON_ICC"
+        android:protectionLevel="signature|telephony" />
+
     <!-- Used for runtime permissions related to user's SMS messages. -->
     <permission-group android:name="android.permission-group.SMS"
         android:icon="@drawable/perm_group_sms"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index b17d473..6350bf6 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2497,6 +2497,30 @@
             <enum name="hdr" value="2" />
         </attr>
         <attr name="forceQueryable" format="boolean" />
+        <!-- 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.
+
+             <p> This setting should be used when low latency has a higher priority than
+             image enhancement processing (e.g. for games or video conferencing).
+
+             <p> If the Display sink is connected via HDMI, the device will begin to
+             send infoframes with Auto Low Latency Mode enabled and Game Content Type.
+             This will switch the connected display to a minimal image processing  mode
+             (if available), which reduces latency, improving the user experience for
+             gaming or video conferencing applications. For more information,
+             see HDMI 2.1 specification.
+
+             <p> If the Display sink has an internal connection or uses some other
+             protocol than HDMI, effects may be similar but implementation-defined.
+
+             <p> The ability to switch to a mode with minimal post proessing may be
+             disabled by a user setting in the system settings menu. In that case,
+             this field is ignored and the display will remain in its current
+             mode.
+
+             <p> See {@link android.content.pm.ActivityInfo #preferMinimalPostProcessing} -->
+        <attr name="preferMinimalPostProcessing" format="boolean"/>
     </declare-styleable>
 
     <!-- The <code>activity-alias</code> tag declares a new
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f28f08b..9073a02 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3560,13 +3560,14 @@
          set to empty string), a default textclassifier will be loaded in the calling app's process.
          See android.view.textclassifier.TextClassificationManager.
     -->
+    <!-- TODO(b/144896755) remove the config -->
     <string name="config_defaultTextClassifierPackage" translatable="false"></string>
 
-    <!-- A list of the default system textclassifier service package name. Only one of the packages
-         will be activated and the fist package is the default system textclassifier service. OS
-         only tries to bind the first trusted service and the others can be selected via device
-         config. These services must be trusted, as they can be activated without explicit consent
-         of the user. Example: "com.android.textclassifier"
+    <!-- A list of supported system textClassifier service package names. Only one of the packages
+         will be activated. The first package in the list is the default system textClassifier
+         service. OS only tries to bind and grant permissions to the first trusted service and the
+         others can be selected via device config. These services must be trusted, as they can be
+         activated without explicit consent of the user. Example: "com.android.textclassifier"
     -->
     <string-array name="config_defaultTextClassifierPackages" translatable="false">
         <item>android.ext.services</item>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 4bbfeaf..0b63518 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3049,6 +3049,10 @@
       <public name="accessibilitySystemActionLockScreen" />
       <public name="accessibilitySystemActionTakeScreenshot" />
     </public-group>
+
+    <public-group type="attr" first-id="0x0101060c">
+      <public name="preferMinimalPostProcessing"/>
+    </public-group>
   <!-- ===============================================================
        DO NOT ADD UN-GROUPED ITEMS HERE
 
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index c7e54f3..1aea98a 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1399,6 +1399,7 @@
         </activity>
 
         <activity android:name="android.app.activity.ActivityThreadTest$TestActivity"
+            android:supportsPictureInPicture="true"
             android:exported="true">
         </activity>
 
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index c50cbe3..beaaa37 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -25,10 +25,12 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertFalse;
 
 import android.app.Activity;
 import android.app.ActivityThread;
 import android.app.IApplicationThread;
+import android.app.PictureInPictureParams;
 import android.app.servertransaction.ActivityConfigurationChangeItem;
 import android.app.servertransaction.ActivityRelaunchItem;
 import android.app.servertransaction.ClientTransaction;
@@ -332,6 +334,50 @@
         assertThat(activity.isResumed()).isTrue();
     }
 
+    @Test
+    public void testHandlePictureInPictureRequested_overriddenToEnter() {
+        final Intent startIntent = new Intent();
+        startIntent.putExtra(TestActivity.PIP_REQUESTED_OVERRIDE_ENTER, true);
+        final TestActivity activity = mActivityTestRule.launchActivity(startIntent);
+        final ActivityThread activityThread = activity.getActivityThread();
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            activityThread.handlePictureInPictureRequested(activity.getActivityToken());
+        });
+
+        assertTrue(activity.pipRequested());
+        assertTrue(activity.enteredPip());
+    }
+
+    @Test
+    public void testHandlePictureInPictureRequested_overriddenToSkip() {
+        final Intent startIntent = new Intent();
+        startIntent.putExtra(TestActivity.PIP_REQUESTED_OVERRIDE_SKIP, true);
+        final TestActivity activity = mActivityTestRule.launchActivity(startIntent);
+        final ActivityThread activityThread = activity.getActivityThread();
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            activityThread.handlePictureInPictureRequested(activity.getActivityToken());
+        });
+
+        assertTrue(activity.pipRequested());
+        assertTrue(activity.enterPipSkipped());
+    }
+
+    @Test
+    public void testHandlePictureInPictureRequested_notOverridden() {
+        final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
+        final ActivityThread activityThread = activity.getActivityThread();
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            activityThread.handlePictureInPictureRequested(activity.getActivityToken());
+        });
+
+        assertTrue(activity.pipRequested());
+        assertFalse(activity.enteredPip());
+        assertFalse(activity.enterPipSkipped());
+    }
+
     /**
      * Calls {@link ActivityThread#handleActivityConfigurationChanged(IBinder, Configuration, int)}
      * to try to push activity configuration to the activity for the given sequence number.
@@ -428,9 +474,16 @@
 
     // Test activity
     public static class TestActivity extends Activity {
+        static final String PIP_REQUESTED_OVERRIDE_ENTER = "pip_requested_override_enter";
+        static final String PIP_REQUESTED_OVERRIDE_SKIP = "pip_requested_override_skip";
+
         int mNumOfConfigChanges;
         final Configuration mConfig = new Configuration();
 
+        private boolean mPipRequested;
+        private boolean mPipEntered;
+        private boolean mPipEnterSkipped;
+
         /**
          * A latch used to notify tests that we're about to wait for configuration latch. This
          * is used to notify test code that preExecute phase for activity configuration change
@@ -460,5 +513,29 @@
                 }
             }
         }
+
+        @Override
+        public void onPictureInPictureRequested() {
+            mPipRequested = true;
+            if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_ENTER, false)) {
+                enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+                mPipEntered = true;
+            } else if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_SKIP, false)) {
+                mPipEnterSkipped = true;
+            }
+            super.onPictureInPictureRequested();
+        }
+
+        boolean pipRequested() {
+            return mPipRequested;
+        }
+
+        boolean enteredPip() {
+            return mPipEntered;
+        }
+
+        boolean enterPipSkipped() {
+            return mPipEnterSkipped;
+        }
     }
 }
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
index b9cc8f4..ec5813f 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
@@ -29,6 +29,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -77,6 +80,7 @@
         final TextLinks result = TextLinks.CREATOR.createFromParcel(parcel);
         final List<TextLinks.TextLink> resultList = new ArrayList<>(result.getLinks());
 
+        assertEquals(fullText, result.getText());
         assertEquals(2, resultList.size());
         assertEquals(0, resultList.get(0).getStart());
         assertEquals(4, resultList.get(0).getEnd());
@@ -103,10 +107,13 @@
                 Arrays.asList(TextClassifier.HINT_TEXT_IS_EDITABLE),
                 Arrays.asList("a", "b", "c"),
                 Arrays.asList("b"));
+        final ZonedDateTime referenceTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(1000L),
+                ZoneId.of("UTC"));
         final TextLinks.Request reference = new TextLinks.Request.Builder("text")
                 .setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY))
                 .setEntityConfig(entityConfig)
                 .setExtras(BUNDLE)
+                .setReferenceTime(referenceTime)
                 .build();
         reference.setCallingPackageName(packageName);
 
@@ -124,5 +131,6 @@
                 result.getEntityConfig().resolveEntityListModifications(Collections.emptyList()));
         assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY));
         assertEquals(packageName, result.getCallingPackageName());
+        assertEquals(referenceTime, result.getReferenceTime());
     }
 }
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 20395fb..1d3a399 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -28,6 +28,13 @@
 }
 
 prebuilt_etc {
+    name: "preinstalled-packages-platform-overlays.xml",
+    product_specific: true,
+    sub_dir: "sysconfig",
+    src: "preinstalled-packages-platform-overlays.xml",
+}
+
+prebuilt_etc {
     name: "hiddenapi-package-whitelist.xml",
     sub_dir: "sysconfig",
     src: "hiddenapi-package-whitelist.xml",
diff --git a/data/etc/preinstalled-packages-platform-overlays.xml b/data/etc/preinstalled-packages-platform-overlays.xml
new file mode 100644
index 0000000..1724715
--- /dev/null
+++ b/data/etc/preinstalled-packages-platform-overlays.xml
@@ -0,0 +1,47 @@
+<?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.
+-->
+<!-- System packages to preinstall on all devices with frameworks-base-overlays, per user type.
+     Documentation at frameworks/base/data/etc/preinstalled-packages-platform.xml
+-->
+<config>
+    <install-in-user-type package="com.android.internal.display.cutout.emulation.corner">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.internal.display.cutout.emulation.double">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.internal.display.cutout.emulation.tall">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.internal.systemui.navbar.gestural">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.internal.systemui.navbar.gestural_extra_wide_back">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.internal.systemui.navbar.gestural_narrow_back">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.internal.systemui.navbar.gestural_wide_back">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.internal.systemui.navbar.threebutton">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+    <install-in-user-type package="com.android.internal.systemui.navbar.twobutton">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
+</config>
diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml
index 604b407..efab27f 100644
--- a/data/etc/preinstalled-packages-platform.xml
+++ b/data/etc/preinstalled-packages-platform.xml
@@ -106,4 +106,7 @@
         <install-in user-type="FULL" />
         <install-in user-type="PROFILE" />
     </install-in-user-type>
+    <install-in-user-type package="com.android.wallpaperbackup">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
 </config>
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index eb469a8..c1fed26 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -22,7 +22,6 @@
 #include <log/log.h>
 #include <private/gui/SyncFeatures.h>
 #include <sync/sync.h>
-#include <system/window.h>
 #include <utils/Trace.h>
 
 #include <string>
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index bbffb35..a7ea21d 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -29,12 +29,12 @@
 
 static int InvertTransform(int transform) {
     switch (transform) {
-        case NATIVE_WINDOW_TRANSFORM_ROT_90:
-            return NATIVE_WINDOW_TRANSFORM_ROT_270;
-        case NATIVE_WINDOW_TRANSFORM_ROT_180:
-            return NATIVE_WINDOW_TRANSFORM_ROT_180;
-        case NATIVE_WINDOW_TRANSFORM_ROT_270:
-            return NATIVE_WINDOW_TRANSFORM_ROT_90;
+        case ANATIVEWINDOW_TRANSFORM_ROTATE_90:
+            return ANATIVEWINDOW_TRANSFORM_ROTATE_270;
+        case ANATIVEWINDOW_TRANSFORM_ROTATE_180:
+            return ANATIVEWINDOW_TRANSFORM_ROTATE_180;
+        case ANATIVEWINDOW_TRANSFORM_ROTATE_270:
+            return ANATIVEWINDOW_TRANSFORM_ROTATE_90;
         default:
             return 0;
     }
@@ -47,11 +47,11 @@
     switch (transform) {
         case 0:
             return SkMatrix::I();
-        case NATIVE_WINDOW_TRANSFORM_ROT_90:
+        case ANATIVEWINDOW_TRANSFORM_ROTATE_90:
             return SkMatrix::MakeAll(0, -1, height, 1, 0, 0, 0, 0, 1);
-        case NATIVE_WINDOW_TRANSFORM_ROT_180:
+        case ANATIVEWINDOW_TRANSFORM_ROTATE_180:
             return SkMatrix::MakeAll(-1, 0, width, 0, -1, height, 0, 0, 1);
-        case NATIVE_WINDOW_TRANSFORM_ROT_270:
+        case ANATIVEWINDOW_TRANSFORM_ROTATE_270:
             return SkMatrix::MakeAll(0, 1, 0, -1, 0, width, 0, 0, 1);
         default:
             LOG_ALWAYS_FATAL("Unsupported Window Transform (%d)", transform);
@@ -168,7 +168,7 @@
     outWindowInfo->transform = query_value;
 
     outWindowInfo->actualSize = outWindowInfo->size;
-    if (outWindowInfo->transform & NATIVE_WINDOW_TRANSFORM_ROT_90) {
+    if (outWindowInfo->transform & ANATIVEWINDOW_TRANSFORM_ROTATE_90) {
         outWindowInfo->actualSize.set(outWindowInfo->size.height(), outWindowInfo->size.width());
     }
 
diff --git a/media/Android.bp b/media/Android.bp
index 75ccb22..a136517 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -26,7 +26,8 @@
 
     installable: true,
 
-    // Make sure that the implementaion only relies on SDK or system APIs.
+    // TODO: build against stable API surface. Use core_platform for now to avoid
+    // link-check failure with exoplayer building against "current".
     sdk_version: "core_platform",
     libs: [
         // The order matters. android_system_* library should come later.
@@ -101,6 +102,7 @@
     name: "framework_media_annotation",
     srcs: [":framework-media-annotation-srcs"],
     installable: false,
+    sdk_version: "core_current",
 }
 
 aidl_interface {
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index 489d050..7ff15df 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -281,6 +281,8 @@
      * metadata (object audio) over HDMI (e.g. Dolby Atmos content).
      **/
     public static final int ENCODING_DOLBY_MAT = 19;
+    /** Audio data format: OPUS compressed. */
+    public static final int ENCODING_OPUS = 20;
 
     /** @hide */
     public static String toLogFriendlyEncoding(int enc) {
@@ -323,6 +325,8 @@
                 return "ENCODING_E_AC3_JOC";
             case ENCODING_DOLBY_MAT:
                 return "ENCODING_DOLBY_MAT";
+            case ENCODING_OPUS:
+                return "ENCODING_OPUS";
             default :
                 return "invalid encoding " + enc;
         }
@@ -548,6 +552,7 @@
             case ENCODING_AC4:
             case ENCODING_E_AC3_JOC:
             case ENCODING_DOLBY_MAT:
+            case ENCODING_OPUS:
                 return true;
             default:
                 return false;
@@ -576,6 +581,7 @@
             case ENCODING_AC4:
             case ENCODING_E_AC3_JOC:
             case ENCODING_DOLBY_MAT:
+            case ENCODING_OPUS:
                 return true;
             default:
                 return false;
@@ -607,6 +613,7 @@
             case ENCODING_AC4:
             case ENCODING_E_AC3_JOC:
             case ENCODING_DOLBY_MAT:
+            case ENCODING_OPUS:
                 return false;
             case ENCODING_INVALID:
             default:
@@ -638,6 +645,7 @@
             case ENCODING_AC4:
             case ENCODING_E_AC3_JOC:
             case ENCODING_DOLBY_MAT:
+            case ENCODING_OPUS:
                 return false;
             case ENCODING_INVALID:
             default:
@@ -917,6 +925,7 @@
                 case ENCODING_AC4:
                 case ENCODING_E_AC3_JOC:
                 case ENCODING_DOLBY_MAT:
+                case ENCODING_OPUS:
                     mEncoding = encoding;
                     break;
                 case ENCODING_INVALID:
@@ -1136,7 +1145,8 @@
         ENCODING_AAC_XHE,
         ENCODING_AC4,
         ENCODING_E_AC3_JOC,
-        ENCODING_DOLBY_MAT }
+        ENCODING_DOLBY_MAT,
+        ENCODING_OPUS }
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface Encoding {}
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index ab80b3a..515e9d0 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -400,6 +400,7 @@
      * configurations
      * @return true if active
      */
+    @SystemApi
     public boolean isActive() {
         switch (mPlayerState) {
             case PLAYER_STATE_STARTED:
@@ -420,18 +421,7 @@
      * @param pw
      */
     public void dump(PrintWriter pw) {
-        pw.println("  " + toLogFriendlyString(this));
-    }
-
-    /**
-     * @hide
-     */
-    public static String toLogFriendlyString(AudioPlaybackConfiguration apc) {
-        return new String("ID:" + apc.mPlayerIId
-                + " -- type:" + toLogFriendlyPlayerType(apc.mPlayerType)
-                + " -- u/pid:" + apc.mClientUid +"/" + apc.mClientPid
-                + " -- state:" + toLogFriendlyPlayerState(apc.mPlayerState)
-                + " -- attr:" + apc.mPlayerAttr);
+        pw.println("  " + this);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<AudioPlaybackConfiguration> CREATOR
@@ -498,6 +488,15 @@
                 && (mClientPid == that.mClientPid));
     }
 
+    @Override
+    public String toString() {
+        return "AudioPlaybackConfiguration piid:" + mPlayerIId
+                + " type:" + toLogFriendlyPlayerType(mPlayerType)
+                + " u/pid:" + mClientUid + "/" + mClientPid
+                + " state:" + toLogFriendlyPlayerState(mPlayerState)
+                + " attr:" + mPlayerAttr;
+    }
+
     //=====================================================================
     // Inner class for corresponding IPlayer and its death monitoring
     static final class IPlayerShell implements IBinder.DeathRecipient {
diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl
index d8fd1ff..a03aa86 100644
--- a/media/java/android/media/IMediaRoute2Provider.aidl
+++ b/media/java/android/media/IMediaRoute2Provider.aidl
@@ -24,6 +24,8 @@
  */
 oneway interface IMediaRoute2Provider {
     void setClient(IMediaRoute2ProviderClient client);
+    void requestCreateSession(String packageName, String routeId, String controlCategory,
+            int requestId);
     void requestSelectRoute(String packageName, String id, int seq);
     void unselectRoute(String packageName, String id);
     void notifyControlRequestSent(String id, in Intent request);
diff --git a/media/java/android/media/IMediaRoute2ProviderClient.aidl b/media/java/android/media/IMediaRoute2ProviderClient.aidl
index f4fb7f4..657f703 100644
--- a/media/java/android/media/IMediaRoute2ProviderClient.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderClient.aidl
@@ -18,6 +18,7 @@
 
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2Info;
+import android.media.RouteSessionInfo;
 import android.os.Bundle;
 
 /**
@@ -25,5 +26,7 @@
  */
 oneway interface IMediaRoute2ProviderClient {
     void updateProviderInfo(in MediaRoute2ProviderInfo info);
-    void notifyRouteSelected(String packageName, String routeId, in Bundle controlHints, int seq);
+    void notifyRouteSelected(String packageName, String routeId, in @nullable Bundle controlHints,
+            int seq);
+    void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, int requestId);
 }
diff --git a/media/java/android/media/IMediaRouter2Client.aidl b/media/java/android/media/IMediaRouter2Client.aidl
index b04af7d..293116c1 100644
--- a/media/java/android/media/IMediaRouter2Client.aidl
+++ b/media/java/android/media/IMediaRouter2Client.aidl
@@ -17,6 +17,7 @@
 package android.media;
 
 import android.media.MediaRoute2Info;
+import android.media.RouteSessionInfo;
 import android.os.Bundle;
 
 /**
@@ -27,5 +28,5 @@
     void notifyRoutesAdded(in List<MediaRoute2Info> routes);
     void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
     void notifyRoutesChanged(in List<MediaRoute2Info> routes);
-    void notifyRouteSelected(in MediaRoute2Info route, int reason, in Bundle controlHints);
+    void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, int requestId);
 }
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index d803f04..faf2563 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -48,13 +48,9 @@
     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);
-    /**
-     * Changes the selected route of the client.
-     *
-     * @param client the client that changes it's selected route
-     * @param route the route to be selected
-     */
-    void requestSelectRoute2(IMediaRouter2Client client, in @nullable MediaRoute2Info route);
+
+    void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route,
+            String controlCategory, int requestId);
     void setControlCategories(IMediaRouter2Client client, in List<String> categories);
 
     void registerManager(IMediaRouter2Manager manager, String packageName);
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 7008d32..d919d78 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -16,13 +16,17 @@
 
 package android.media;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -46,6 +50,34 @@
         }
     };
 
+    /** @hide */
+    @IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
+            CONNECTION_STATE_CONNECTED})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface ConnectionState {}
+
+    /**
+     * The default connection state indicating the route is disconnected.
+     *
+     * @see #getConnectionState
+     */
+    public static final int CONNECTION_STATE_DISCONNECTED = 0;
+
+    /**
+     * A connection state indicating the route is in the process of connecting and is not yet
+     * ready for use.
+     *
+     * @see #getConnectionState
+     */
+    public static final int CONNECTION_STATE_CONNECTING = 1;
+
+    /**
+     * A connection state indicating the route is connected.
+     *
+     * @see #getConnectionState
+     */
+    public static final int CONNECTION_STATE_CONNECTED = 2;
+
     /**
      * Playback information indicating the playback volume is fixed, i&#46;e&#46; it cannot be
      * controlled from this object. An example of fixed playback volume is a remote player,
@@ -61,6 +93,46 @@
      */
     public static final int PLAYBACK_VOLUME_VARIABLE = 1;
 
+    /** @hide */
+    @IntDef({
+            DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_TV,
+            DEVICE_TYPE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
+    @Retention(RetentionPolicy.SOURCE)
+    private @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;
+
+    /**
+     * A receiver device type of the route indicating the presentation of the media is happening
+     * on a TV.
+     *
+     * @see #getDeviceType
+     */
+    public static final int DEVICE_TYPE_TV = 1;
+
+    /**
+     * A receiver device type of the route indicating the presentation of the media is happening
+     * on a speaker.
+     *
+     * @see #getDeviceType
+     */
+    public static final int DEVICE_TYPE_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
     final String mId;
     @Nullable
@@ -70,12 +142,17 @@
     @Nullable
     final CharSequence mDescription;
     @Nullable
+    final @ConnectionState int mConnectionState;
+    @Nullable
+    final Uri mIconUri;
+    @Nullable
     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;
 
@@ -86,11 +163,14 @@
         mProviderId = builder.mProviderId;
         mName = builder.mName;
         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;
         mExtras = builder.mExtras;
         mUniqueId = createUniqueId();
     }
@@ -100,11 +180,14 @@
         mProviderId = in.readString();
         mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         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();
         mExtras = in.readBundle();
         mUniqueId = createUniqueId();
     }
@@ -145,18 +228,22 @@
                 && Objects.equals(mProviderId, other.mProviderId)
                 && Objects.equals(mName, other.mName)
                 && 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);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mId, mName, mDescription, mSupportedCategories);
+        return Objects.hash(mId, mName, mDescription, mConnectionState, mIconUri,
+                mSupportedCategories, mVolume, mVolumeMax, mVolumeHandling, mDeviceType);
     }
 
     /**
@@ -204,6 +291,29 @@
     }
 
     /**
+     * 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
@@ -221,6 +331,18 @@
         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.
      */
@@ -293,11 +415,14 @@
         dest.writeString(mProviderId);
         TextUtils.writeToParcel(mName, dest, 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.writeBundle(mExtras);
     }
 
@@ -308,9 +433,12 @@
                 .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();
@@ -324,11 +452,16 @@
         String mProviderId;
         CharSequence mName;
         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;
         Bundle mExtras;
 
         public Builder(@NonNull String id, @NonNull CharSequence name) {
@@ -348,11 +481,14 @@
             }
             setName(routeInfo.mName);
             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);
             if (routeInfo.mExtras != null) {
                 mExtras = new Bundle(routeInfo.mExtras);
             }
@@ -403,6 +539,39 @@
         }
 
         /**
+        * 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 URI of the icon representing this route.
+         * <p>
+         * This icon will be used in picker UIs if available.
+         * </p><p>
+         * The URI must be one of the following formats:
+         * <ul>
+         * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
+         * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})
+         * </li>
+         * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
+         * </ul>
+         * </p>
+         */
+        @NonNull
+        public Builder setIconUri(@Nullable Uri iconUri) {
+            mIconUri = iconUri;
+            return this;
+        }
+
+        /**
          * Sets the package name of the app using the route.
          */
         @NonNull
@@ -470,6 +639,16 @@
             mVolumeHandling = volumeHandling;
             return this;
         }
+
+        /**
+         * Sets the route's device type.
+         */
+        @NonNull
+        public Builder setDeviceType(@DeviceType int deviceType) {
+            mDeviceType = deviceType;
+            return this;
+        }
+
         /**
          * Sets a bundle of extras for the route.
          */
diff --git a/media/java/android/media/MediaRoute2ProviderInfo.java b/media/java/android/media/MediaRoute2ProviderInfo.java
index b5de88a..7078d4a 100644
--- a/media/java/android/media/MediaRoute2ProviderInfo.java
+++ b/media/java/android/media/MediaRoute2ProviderInfo.java
@@ -46,9 +46,9 @@
     };
 
     @Nullable
-    private final String mUniqueId;
+    final String mUniqueId;
     @NonNull
-    private final ArrayMap<String, MediaRoute2Info> mRoutes;
+    final ArrayMap<String, MediaRoute2Info> mRoutes;
 
     MediaRoute2ProviderInfo(@NonNull Builder builder) {
         Objects.requireNonNull(builder, "builder must not be null.");
@@ -142,6 +142,7 @@
         public Builder(@NonNull MediaRoute2ProviderInfo descriptor) {
             Objects.requireNonNull(descriptor, "descriptor must not be null");
 
+            mUniqueId = descriptor.mUniqueId;
             mRoutes = new ArrayMap<>(descriptor.mRoutes);
         }
 
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 1b6183e..387b042 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -27,8 +27,13 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -40,10 +45,15 @@
     public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
 
     private final Handler mHandler;
+    private final Object mSessionLock = new Object();
     private ProviderStub mStub;
+    // TODO: Rename this to mService (and accordingly IMediaRoute2ProviderClient to something else)
     private IMediaRoute2ProviderClient mClient;
     private MediaRoute2ProviderInfo mProviderInfo;
 
+    @GuardedBy("mSessionLock")
+    private ArrayMap<Integer, RouteSessionInfo> mSessionInfo = new ArrayMap<>();
+
     public MediaRoute2ProviderService() {
         mHandler = new Handler(Looper.getMainLooper());
     }
@@ -93,6 +103,7 @@
 
     /**
      * Called when requestSetVolume is called on a route of the provider
+     *
      * @param routeId the id of the route
      * @param volume the target volume
      */
@@ -100,15 +111,174 @@
 
     /**
      * Called when requestUpdateVolume is called on a route of the provider
+     *
      * @param routeId id of the route
      * @param delta the delta to add to the current volume
      */
     public abstract void onUpdateVolume(@NonNull String routeId, int delta);
 
     /**
-     * Updates provider info and publishes routes
+     * Gets information of the session with the given id.
+     *
+     * @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.
      */
-    public final void setProviderInfo(MediaRoute2ProviderInfo info) {
+    @Nullable
+    public final RouteSessionInfo getSessionInfo(int sessionId) {
+        synchronized (mSessionLock) {
+            return mSessionInfo.get(sessionId);
+        }
+    }
+
+    /**
+     * Gets the list of {@link RouteSessionInfo session info} that the provider service maintains.
+     */
+    @NonNull
+    public final List<RouteSessionInfo> 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, int)
+     */
+    public final void updateSessionInfo(@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.");
+            }
+        }
+    }
+
+    /**
+     * 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.
+     *
+     * @param sessionInfo information of the new session.
+     *                    Pass {@code null} to reject the request or inform clients that
+     *                    session creation has failed.
+     * @param requestId id of the previous request to create this session
+     */
+    //TODO: fail reason?
+    public final void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, int requestId) {
+        //TODO: validate sessionInfo.getSessionId() (it must be in "waiting list")
+        if (sessionInfo != null) {
+            synchronized (mSessionLock) {
+                mSessionInfo.put(sessionInfo.getSessionId(), sessionInfo);
+            }
+        }
+
+        if (mClient == null) {
+            return;
+        }
+        try {
+            mClient.notifySessionCreated(sessionInfo, requestId);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Failed to notify session created.");
+        }
+    }
+
+    /**
+     * Releases a session with the given id.
+     * {@link #onDestroySession} is called if the session is released.
+     *
+     * @param sessionId id of the session to be released
+     * @see #onDestroySession(int, RouteSessionInfo)
+     */
+    public final void releaseSession(int sessionId) {
+        RouteSessionInfo sessionInfo;
+        synchronized (mSessionLock) {
+            sessionInfo = mSessionInfo.put(sessionId, null);
+        }
+        if (sessionInfo != null) {
+            mHandler.sendMessage(obtainMessage(
+                    MediaRoute2ProviderService::onDestroySession, this, sessionId, sessionInfo));
+        }
+    }
+
+    /**
+     * Called when a session should be created.
+     * You should create and maintain your own session and notifies the client of
+     * session info. Call {@link #notifySessionCreated(RouteSessionInfo, int)}
+     * 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, int)}
+     * 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
+     */
+    public abstract void onCreateSession(@NonNull String packageName, @NonNull String routeId,
+            @NonNull String controlCategory, int requestId);
+
+    /**
+     * 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.
+     *
+     * @param sessionId id of the session being destroyed.
+     * @param lastSessionInfo information of the session being destroyed.
+     * @see #releaseSession(int)
+     */
+    public abstract void onDestroySession(int sessionId, @NonNull RouteSessionInfo lastSessionInfo);
+
+    //TODO: make a way to reject the request
+    /**
+     * Called when a client requests adding a route to a session.
+     * After the route is added, call {@link #updateSessionInfo(RouteSessionInfo)} to update
+     * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
+     * clients of updated session info.
+     *
+     * @param sessionId id of the session
+     * @param routeId id of the route
+     * @see #updateSessionInfo(RouteSessionInfo)
+     */
+    public abstract void onAddRoute(int sessionId, @NonNull String routeId);
+
+    //TODO: make a way to reject the request
+    /**
+     * Called when a client requests removing a route from a session.
+     * After the route is removed, call {@link #updateSessionInfo(RouteSessionInfo)} to update
+     * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
+     * clients of updated session info.
+     *
+     * @param sessionId id of the session
+     * @param routeId id of the route
+     */
+    public abstract void onRemoveRoute(int 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.
+     *
+     * @param sessionId id of the session
+     * @param routeId id of the route
+     */
+    public abstract void onTransferRoute(int sessionId, @NonNull String routeId);
+
+    /**
+     * Updates provider info and publishes routes and session info.
+     */
+    public final void updateProviderInfo(MediaRoute2ProviderInfo info) {
         mProviderInfo = info;
         publishState();
     }
@@ -142,6 +312,7 @@
     }
 
     void publishState() {
+        //TODO: sends session info
         if (mClient == null) {
             return;
         }
@@ -179,35 +350,43 @@
         }
 
         @Override
-        public void requestSelectRoute(String packageName, String id, int seq) {
+        public void requestCreateSession(String packageName, String routeId, String controlCategory,
+                int requestId) {
+            mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession,
+                    MediaRoute2ProviderService.this, packageName, routeId, controlCategory,
+                    requestId));
+        }
+
+        @Override
+        public void requestSelectRoute(String packageName, String routeId, int seq) {
+            //TODO: call onCreateSession instead
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute,
-                    MediaRoute2ProviderService.this, packageName, id,
-                    new SelectToken(packageName, id, seq)));
-
+                    MediaRoute2ProviderService.this, packageName, routeId,
+                    new SelectToken(packageName, routeId, seq)));
         }
 
         @Override
-        public void unselectRoute(String packageName, String id) {
+        public void unselectRoute(String packageName, String routeId) {
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onUnselectRoute,
-                    MediaRoute2ProviderService.this, packageName, id));
+                    MediaRoute2ProviderService.this, packageName, routeId));
         }
 
         @Override
-        public void notifyControlRequestSent(String id, Intent request) {
+        public void notifyControlRequestSent(String routeId, Intent request) {
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onControlRequest,
-                    MediaRoute2ProviderService.this, id, request));
+                    MediaRoute2ProviderService.this, routeId, request));
         }
 
         @Override
-        public void requestSetVolume(String id, int volume) {
+        public void requestSetVolume(String routeId, int volume) {
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetVolume,
-                    MediaRoute2ProviderService.this, id, volume));
+                    MediaRoute2ProviderService.this, routeId, volume));
         }
 
         @Override
-        public void requestUpdateVolume(String id, int delta) {
+        public void requestUpdateVolume(String routeId, int delta) {
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onUpdateVolume,
-                    MediaRoute2ProviderService.this, id, delta));
+                    MediaRoute2ProviderService.this, routeId, delta));
         }
     }
 }
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index f9dabcf..d58a105 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -29,6 +29,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.text.TextUtils;
@@ -46,6 +47,7 @@
 import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * A new Media Router
@@ -98,7 +100,10 @@
     private final Context mContext;
     private final IMediaRouterService mMediaRouterService;
 
-    private final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords =
+    private final CopyOnWriteArrayList<RouteCallbackRecord> mRouteCallbackRecords =
+            new CopyOnWriteArrayList<>();
+
+    private final CopyOnWriteArrayList<SessionCreationRequest> mSessionCreationRequests =
             new CopyOnWriteArrayList<>();
 
     private final String mPackageName;
@@ -108,12 +113,12 @@
     @GuardedBy("sLock")
     private List<String> mControlCategories = Collections.emptyList();
 
-    private MediaRoute2Info mSelectedRoute;
-    @GuardedBy("sLock")
-    private MediaRoute2Info mSelectingRoute;
+    // TODO: Make MediaRouter2 is always connected to the MediaRouterService.
     @GuardedBy("sLock")
     private Client2 mClient;
 
+    private AtomicInteger mSessionCreationRequestCnt = new AtomicInteger(1);
+
     final Handler mHandler;
     @GuardedBy("sLock")
     private boolean mShouldUpdateRoutes;
@@ -154,18 +159,14 @@
         for (MediaRoute2Info route : currentSystemRoutes) {
             mRoutes.put(route.getId(), route);
         }
-        // The first route is 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.
-        mSelectedRoute = currentSystemRoutes.get(0);
     }
 
     /**
      * Registers a callback to discover routes and to receive events when they change.
      */
     public void registerCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull Callback callback) {
-        registerCallback(executor, callback, 0);
+            @NonNull RouteCallback routeCallback) {
+        registerCallback(executor, routeCallback, 0);
     }
 
     /**
@@ -175,12 +176,12 @@
      * </p>
      */
     public void registerCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull Callback callback, int flags) {
+            @NonNull RouteCallback routeCallback, int flags) {
         Objects.requireNonNull(executor, "executor must not be null");
-        Objects.requireNonNull(callback, "callback must not be null");
+        Objects.requireNonNull(routeCallback, "callback must not be null");
 
-        CallbackRecord record = new CallbackRecord(callback, executor, flags);
-        if (!mCallbackRecords.addIfAbsent(record)) {
+        RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, flags);
+        if (!mRouteCallbackRecords.addIfAbsent(record)) {
             Log.w(TAG, "Ignoring the same callback");
             return;
         }
@@ -205,19 +206,20 @@
      * Unregisters the given callback. The callback will no longer receive events.
      * If the callback has not been added or been removed already, it is ignored.
      *
-     * @param callback the callback to unregister
+     * @param routeCallback the callback to unregister
      * @see #registerCallback
      */
-    public void unregisterCallback(@NonNull Callback callback) {
-        Objects.requireNonNull(callback, "callback must not be null");
+    public void unregisterCallback(@NonNull RouteCallback routeCallback) {
+        Objects.requireNonNull(routeCallback, "callback must not be null");
 
-        if (!mCallbackRecords.remove(new CallbackRecord(callback, null, 0))) {
+        if (!mRouteCallbackRecords.remove(
+                new RouteCallbackRecord(null, routeCallback, 0))) {
             Log.w(TAG, "Ignoring unknown callback");
             return;
         }
 
         synchronized (sLock) {
-            if (mCallbackRecords.size() == 0 && mClient != null) {
+            if (mRouteCallbackRecords.size() == 0 && mClient != null) {
                 try {
                     mMediaRouterService.unregisterClient2(mClient);
                 } catch (RemoteException ex) {
@@ -282,38 +284,51 @@
     }
 
     /**
-     * Gets the currently selected route.
+     * Requests the media route provider service to create a session with the given route.
      *
-     * @return the selected route
+     * @param route the route you want to create a session with.
+     * @param controlCategory the control category of the session. Should not be empty
+     * @param executor the executor to get the result of the session creation
+     * @param callback the callback to get the result of the session creation
+     *
+     * @see SessionCreationCallback#onSessionCreated(RouteSessionController, Bundle)
+     * @see SessionCreationCallback#onSessionCreationFailed()
      */
     @NonNull
-    public MediaRoute2Info getSelectedRoute() {
-        return mSelectedRoute;
-    }
-
-    /**
-     * Request to select the specified route. When the route is selected,
-     * {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} will be called.
-     *
-     * @param route the route to select
-     */
-    public void requestSelectRoute(@NonNull MediaRoute2Info route) {
+    public void requestCreateSession(@NonNull MediaRoute2Info route,
+            @NonNull String controlCategory,
+            @CallbackExecutor Executor executor, @NonNull SessionCreationCallback callback) {
         Objects.requireNonNull(route, "route must not be null");
+        if (TextUtils.isEmpty(controlCategory)) {
+            throw new IllegalArgumentException("controlCategory must not be empty");
+        }
+        Objects.requireNonNull(executor, "executor must not be null");
+        Objects.requireNonNull(callback, "callback must not be null");
+
+        // TODO: Check the given route exists
+        // TODO: Check the route supports the given controlCategory
+
+        final int requestId;
+        // TODO: This does not ensure the uniqueness of the request ID.
+        //       Find the way to ensure it. (e.g. have mapping inside MediaRouterService)
+        requestId = Process.myPid() * 10000 + mSessionCreationRequestCnt.getAndIncrement();
+
+        SessionCreationRequest request = new SessionCreationRequest(
+                requestId, route, controlCategory, executor, callback);
+        mSessionCreationRequests.add(request);
 
         Client2 client;
         synchronized (sLock) {
-            if (mSelectingRoute == route) {
-                Log.w(TAG, "The route selection request is already sent.");
-                return;
-            }
-            mSelectingRoute = route;
             client = mClient;
         }
         if (client != null) {
             try {
-                mMediaRouterService.requestSelectRoute2(client, route);
+                mMediaRouterService.requestCreateSession(
+                        client, route, controlCategory, requestId);
             } catch (RemoteException ex) {
-                Log.e(TAG, "Unable to request to select route.", ex);
+                Log.e(TAG, "Unable to request to create session.", ex);
+                mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
+                        MediaRouter2.this, null, requestId));
             }
         }
     }
@@ -472,57 +487,70 @@
         }
     }
 
-    void selectRouteOnHandler(MediaRoute2Info route, int reason, Bundle controlHints) {
-        synchronized (sLock) {
-            if (reason == SELECT_REASON_USER_SELECTED) {
-                if (mSelectingRoute == null
-                        || !TextUtils.equals(mSelectingRoute.getUniqueId(), route.getUniqueId())) {
-                    Log.w(TAG, "Ignoring invalid or outdated notifyRouteSelected call. "
-                            + "selectingRoute=" + mSelectingRoute + " route=" + route);
-                    return;
-                }
+    /**
+     * Creates a controller and calls the {@link SessionCreationCallback#onSessionCreated}.
+     * If session creation has failed, then it calls
+     * {@link SessionCreationCallback#onSessionCreationFailed()}.
+     * <p>
+     * Pass {@code null} to sessionInfo for the failure case.
+     */
+    void createControllerOnHandler(@Nullable RouteSessionInfo sessionInfo, int requestId) {
+        SessionCreationRequest matchingRequest = null;
+        for (SessionCreationRequest request : mSessionCreationRequests) {
+            if (request.mRequestId == requestId) {
+                matchingRequest = request;
+                break;
             }
-            mSelectingRoute = null;
         }
-        if (reason == SELECT_REASON_SYSTEM_SELECTED) {
-            reason = SELECT_REASON_USER_SELECTED;
+
+        if (matchingRequest == null) {
+            Log.w(TAG, "Ignoring session creation result for unknown request."
+                    + " requestId=" + requestId + ", sessionInfo=" + sessionInfo);
+            return;
         }
-        mSelectedRoute = route;
-        notifyRouteSelected(route, reason, controlHints);
+
+        mSessionCreationRequests.remove(matchingRequest);
+
+        final Executor executor = matchingRequest.mExecutor;
+        final SessionCreationCallback callback = matchingRequest.mSessionCreationCallback;
+
+        if (sessionInfo == null) {
+            // TODO: We may need to distinguish between failure and rejection.
+            //       One way can be introducing 'reason'.
+            executor.execute(callback::onSessionCreationFailed);
+        } else {
+            // TODO: RouteSessionController should be created with full info (e.g. routes)
+            //       from RouteSessionInfo.
+            RouteSessionController controller = new RouteSessionController(sessionInfo);
+            executor.execute(() -> callback.onSessionCreated(controller));
+        }
     }
 
     private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
-        for (CallbackRecord record: mCallbackRecords) {
+        for (RouteCallbackRecord record: mRouteCallbackRecords) {
             record.mExecutor.execute(
-                    () -> record.mCallback.onRoutesAdded(routes));
+                    () -> record.mRouteCallback.onRoutesAdded(routes));
         }
     }
 
     private void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
-        for (CallbackRecord record: mCallbackRecords) {
+        for (RouteCallbackRecord record: mRouteCallbackRecords) {
             record.mExecutor.execute(
-                    () -> record.mCallback.onRoutesRemoved(routes));
+                    () -> record.mRouteCallback.onRoutesRemoved(routes));
         }
     }
 
     private void notifyRoutesChanged(List<MediaRoute2Info> routes) {
-        for (CallbackRecord record: mCallbackRecords) {
+        for (RouteCallbackRecord record: mRouteCallbackRecords) {
             record.mExecutor.execute(
-                    () -> record.mCallback.onRoutesChanged(routes));
-        }
-    }
-
-    private void notifyRouteSelected(MediaRoute2Info route, int reason, Bundle controlHints) {
-        for (CallbackRecord record: mCallbackRecords) {
-            record.mExecutor.execute(
-                    () -> record.mCallback.onRouteSelected(route, reason, controlHints));
+                    () -> record.mRouteCallback.onRoutesChanged(routes));
         }
     }
 
     /**
-     * Interface for receiving events about media routing changes.
+     * Callback for receiving events about media route discovery.
      */
-    public static class Callback {
+    public static class RouteCallback {
         /**
          * Called when routes are added. Whenever you registers a callback, this will
          * be invoked with known routes.
@@ -549,30 +577,33 @@
          * @param routes the list of routes that have been changed. It's never empty.
          */
         public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
-
-        /**
-         * Called when a route is selected. Exactly one route can be selected at a time.
-         *
-         * @param route the selected route.
-         * @param reason the reason why the route is selected.
-         * @param controlHints An optional bundle of provider-specific arguments which may be
-         *                     used to control the selected route. Can be empty.
-         * @see #SELECT_REASON_UNKNOWN
-         * @see #SELECT_REASON_USER_SELECTED
-         * @see #SELECT_REASON_FALLBACK
-         * @see #getSelectedRoute()
-         */
-        public void onRouteSelected(@NonNull MediaRoute2Info route, @SelectReason int reason,
-                @NonNull Bundle controlHints) {}
     }
 
-    final class CallbackRecord {
-        public final Callback mCallback;
-        public Executor mExecutor;
-        public int mFlags;
+    /**
+     * Callback for receiving a result of session creation.
+     */
+    public static class SessionCreationCallback {
+        /**
+         * Called when the route session is created by the route provider.
+         *
+         * @param controller the controller to control the created session
+         */
+        public void onSessionCreated(RouteSessionController controller) {}
 
-        CallbackRecord(@NonNull Callback callback, @Nullable Executor executor, int flags) {
-            mCallback = callback;
+        /**
+         * Called when the session creation request failed.
+         */
+        public void onSessionCreationFailed() {}
+    }
+
+    final class RouteCallbackRecord {
+        public final Executor mExecutor;
+        public final RouteCallback mRouteCallback;
+        public final int mFlags;
+
+        RouteCallbackRecord(@Nullable Executor executor, @NonNull RouteCallback routeCallback,
+                int flags) {
+            mRouteCallback = routeCallback;
             mExecutor = executor;
             mFlags = flags;
         }
@@ -582,15 +613,35 @@
             if (this == obj) {
                 return true;
             }
-            if (!(obj instanceof CallbackRecord)) {
+            if (!(obj instanceof RouteCallbackRecord)) {
                 return false;
             }
-            return mCallback == ((CallbackRecord) obj).mCallback;
+            return mRouteCallback
+                    == ((RouteCallbackRecord) obj).mRouteCallback;
         }
 
         @Override
         public int hashCode() {
-            return mCallback.hashCode();
+            return mRouteCallback.hashCode();
+        }
+    }
+
+    final class SessionCreationRequest {
+        public final MediaRoute2Info mRoute;
+        public final String mControlCategory;
+        public final Executor mExecutor;
+        public final SessionCreationCallback mSessionCreationCallback;
+        public final int mRequestId;
+
+        SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route,
+                @NonNull String controlCategory,
+                @Nullable Executor executor,
+                @NonNull SessionCreationCallback sessionCreationCallback) {
+            mRoute = route;
+            mControlCategory = controlCategory;
+            mExecutor = executor;
+            mSessionCreationCallback = sessionCreationCallback;
+            mRequestId = requestId;
         }
     }
 
@@ -617,10 +668,9 @@
         }
 
         @Override
-        public void notifyRouteSelected(MediaRoute2Info route, int reason,
-                Bundle controlHints) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2::selectRouteOnHandler,
-                    MediaRouter2.this, route, reason, controlHints));
+        public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, int requestId) {
+            mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
+                    MediaRouter2.this, sessionInfo, requestId));
         }
     }
 }
diff --git a/media/java/android/media/RouteSessionController.java b/media/java/android/media/RouteSessionController.java
index 5ff7218..95fcdc6 100644
--- a/media/java/android/media/RouteSessionController.java
+++ b/media/java/android/media/RouteSessionController.java
@@ -17,9 +17,13 @@
 package android.media;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -38,6 +42,9 @@
     private final int mSessionId;
     private final String mCategory;
     private final Object mLock = new Object();
+    private final Bundle mControlHints;
+
+    private List<String> mSelectedRoutes;
 
     @GuardedBy("mLock")
     private final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords =
@@ -46,12 +53,14 @@
     private volatile boolean mIsReleased;
 
     /**
-     * @param sessionId the ID of the session.
-     * @param category The category of media routes that the session includes.
+     * @param sessionInfo
      */
-    RouteSessionController(int sessionId, @NonNull String category) {
-        mSessionId = sessionId;
-        mCategory = category;
+    RouteSessionController(@NonNull RouteSessionInfo sessionInfo) {
+        mSessionId = sessionInfo.getSessionId();
+        mCategory = sessionInfo.getControlCategory();
+        mSelectedRoutes = sessionInfo.getSelectedRoutes();
+        mControlHints = sessionInfo.getControlHints();
+        // TODO: Create getters for all other types of routes
     }
 
     /**
@@ -70,12 +79,19 @@
     }
 
     /**
-     * @return the list of currently connected routes
+     * @return the control hints used to control route session if available.
+     */
+    @Nullable
+    public Bundle getControlHints() {
+        return mControlHints;
+    }
+
+    /**
+     * @return the list of currently selected routes
      */
     @NonNull
-    public List<MediaRoute2Info> getRoutes() {
-        // TODO: Implement this when SessionInfo is introduced.
-        return null;
+    public List<String> getSelectedRoutes() {
+        return Collections.unmodifiableList(mSelectedRoutes);
     }
 
     /**
@@ -93,7 +109,7 @@
     /**
      * Add routes to the remote session.
      *
-     * @see #getRoutes()
+     * @see #getSelectedRoutes()
      * @see Callback#onSessionInfoChanged
      */
     public void addRoutes(List<MediaRoute2Info> routes) {
@@ -102,9 +118,10 @@
 
     /**
      * Remove routes from this session. Media may be stopped on those devices.
-     * Route removal requests that are not currently in {@link #getRoutes()} will be ignored.
+     * Route removal requests that are not currently in {@link #getSelectedRoutes()} will be
+     * ignored.
      *
-     * @see #getRoutes()
+     * @see #getSelectedRoutes()
      * @see Callback#onSessionInfoChanged
      */
     public void removeRoutes(List<MediaRoute2Info> routes) {
diff --git a/media/java/android/media/RouteSessionInfo.java b/media/java/android/media/RouteSessionInfo.java
index 0878e6b..c22fc40 100644
--- a/media/java/android/media/RouteSessionInfo.java
+++ b/media/java/android/media/RouteSessionInfo.java
@@ -17,6 +17,8 @@
 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;
@@ -51,6 +53,7 @@
     final List<String> mDeselectableRoutes;
     final List<String> mGroupableRoutes;
     final List<String> mTransferrableRoutes;
+    final Bundle mControlHints;
 
     RouteSessionInfo(@NonNull Builder builder) {
         Objects.requireNonNull(builder, "builder must not be null.");
@@ -63,6 +66,8 @@
         mDeselectableRoutes = Collections.unmodifiableList(builder.mDeselectableRoutes);
         mGroupableRoutes = Collections.unmodifiableList(builder.mGroupableRoutes);
         mTransferrableRoutes = Collections.unmodifiableList(builder.mTransferrableRoutes);
+
+        mControlHints = builder.mControlHints;
     }
 
     RouteSessionInfo(@NonNull Parcel src) {
@@ -76,6 +81,8 @@
         mDeselectableRoutes = ensureList(src.createStringArrayList());
         mGroupableRoutes = ensureList(src.createStringArrayList());
         mTransferrableRoutes = ensureList(src.createStringArrayList());
+
+        mControlHints = src.readBundle();
     }
 
     private static String ensureString(String str) {
@@ -150,6 +157,14 @@
         return mTransferrableRoutes;
     }
 
+    /**
+     * Gets the control hints
+     */
+    @Nullable
+    public Bundle getControlHints() {
+        return mControlHints;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -164,6 +179,7 @@
         dest.writeStringList(mDeselectableRoutes);
         dest.writeStringList(mGroupableRoutes);
         dest.writeStringList(mTransferrableRoutes);
+        dest.writeBundle(mControlHints);
     }
 
     @Override
@@ -192,6 +208,7 @@
         final List<String> mDeselectableRoutes;
         final List<String> mGroupableRoutes;
         final List<String> mTransferrableRoutes;
+        Bundle mControlHints;
 
         public Builder(int sessionId, @NonNull String packageName,
                 @NonNull String controlCategory) {
@@ -215,82 +232,130 @@
             mDeselectableRoutes = new ArrayList<>(sessionInfo.mDeselectableRoutes);
             mGroupableRoutes = new ArrayList<>(sessionInfo.mGroupableRoutes);
             mTransferrableRoutes = new ArrayList<>(sessionInfo.mTransferrableRoutes);
+
+            mControlHints = sessionInfo.mControlHints;
         }
 
         /**
-         * Adds a selected route
+         * Clears the selected routes.
          */
         @NonNull
-        public Builder addSelectedRoute(String routeId) {
-            mSelectedRoutes.add(routeId);
+        public Builder clearSelectedRoutes() {
+            mSelectedRoutes.clear();
             return this;
         }
 
         /**
-         * Removes a selected route
+         * Adds a route to the selected routes.
          */
         @NonNull
-        public Builder removeSelectedRoute(String routeId) {
-            mSelectedRoutes.remove(routeId);
+        public Builder addSelectedRoute(@NonNull String routeId) {
+            mSelectedRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
             return this;
         }
 
         /**
-         * Adds a deselectable route
+         * Removes a route from the selected routes.
          */
         @NonNull
-        public Builder addDeselectableRoute(String routeId) {
-            mDeselectableRoutes.add(routeId);
+        public Builder removeSelectedRoute(@NonNull String routeId) {
+            mSelectedRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
             return this;
         }
 
         /**
-         * Removes a deselecable route
+         * Clears the deselectable routes.
          */
         @NonNull
-        public Builder removeDeselectableRoute(String routeId) {
-            mDeselectableRoutes.remove(routeId);
+        public Builder clearDeselectableRoutes() {
+            mDeselectableRoutes.clear();
             return this;
         }
 
         /**
-         * Adds a groupable route
+         * Adds a route to the deselectable routes.
          */
         @NonNull
-        public Builder addGroupableRoute(String routeId) {
-            mGroupableRoutes.add(routeId);
+        public Builder addDeselectableRoute(@NonNull String routeId) {
+            mDeselectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
             return this;
         }
 
         /**
-         * Removes a groupable route
+         * Removes a route from the deselectable routes.
          */
         @NonNull
-        public Builder removeGroupableRoute(String routeId) {
-            mGroupableRoutes.remove(routeId);
+        public Builder removeDeselectableRoute(@NonNull String routeId) {
+            mDeselectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
             return this;
         }
 
         /**
-         * Adds a transferrable route
+         * Clears the groupable routes.
          */
         @NonNull
-        public Builder addTransferrableRoute(String routeId) {
-            mTransferrableRoutes.add(routeId);
+        public Builder clearGroupableRoutes() {
+            mGroupableRoutes.clear();
             return this;
         }
 
         /**
-         * Removes a transferrable route
+         * Adds a route to the groupable routes.
          */
         @NonNull
-        public Builder removeTransferrableRoute(String routeId) {
-            mTransferrableRoutes.remove(routeId);
+        public Builder addGroupableRoute(@NonNull String routeId) {
+            mGroupableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
             return this;
         }
 
         /**
-         * Builds a route session info
+         * Removes a route from the groupable routes.
+         */
+        @NonNull
+        public Builder removeGroupableRoute(@NonNull String routeId) {
+            mGroupableRoutes.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() {
diff --git a/media/java/android/media/tv/tuner/FilterEvent.java b/media/java/android/media/tv/tuner/FilterEvent.java
new file mode 100644
index 0000000..7c165ce
--- /dev/null
+++ b/media/java/android/media/tv/tuner/FilterEvent.java
@@ -0,0 +1,118 @@
+/*
+ * 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.os.NativeHandle;
+
+/**
+ * Demux filter event.
+ *
+ * @hide
+ */
+public abstract class FilterEvent {
+
+    /**
+     * Section event.
+     */
+    public static class SectionEvent extends FilterEvent {
+        private int mTableId;
+        private int mVersion;
+        private int mSectionNum;
+        private int mDataLength;
+    }
+
+    /**
+     * Media event.
+     */
+    public static 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;
+    }
+
+    /**
+     * PES event.
+     */
+    public static class PesEvent extends FilterEvent {
+        private int mStreamId;
+        private int mDataLength;
+        private int mMpuSequenceNumber;
+    }
+
+    /**
+     * TS record event.
+     */
+    public static class TsRecordEvent extends FilterEvent {
+        private int mTpid;
+        private int mIndexMask;
+        private long mByteNumber;
+    }
+
+    /**
+     * MMPT record event.
+     */
+    public static class MmtpRecordEvent extends FilterEvent {
+        private int mScHevcIndexMask;
+        private long mByteNumber;
+    }
+
+    /**
+     * Download event.
+     */
+    public static class DownloadEvent extends FilterEvent {
+        private int mItemId;
+        private int mMpuSequenceNumber;
+        private int mItemFragmentIndex;
+        private int mLastItemFragmentIndex;
+        private int mDataLength;
+    }
+
+    /**
+     * IP payload event.
+     */
+    public static class IpPayloadEvent extends FilterEvent {
+        private int mDataLength;
+    }
+
+    /**
+     * TEMI event.
+     */
+    public static class TemiEvent extends FilterEvent {
+        private long mPts;
+        private byte mDescrTag;
+        private byte[] mDescrData;
+    }
+
+    /**
+     *  Extra Meta Data from AD (Audio Descriptor) according to
+     *  ETSI TS 101 154 V2.1.1.
+     */
+    public static 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/FrontendStatus.java b/media/java/android/media/tv/tuner/FrontendStatus.java
new file mode 100644
index 0000000..f8b2d12
--- /dev/null
+++ b/media/java/android/media/tv/tuner/FrontendStatus.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 android.media.tv.tuner;
+
+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;
+
+/**
+ * Frontend status
+ *
+ * @hide
+ */
+public class FrontendStatus {
+
+    private final int mType;
+    private final Object mValue;
+
+    private FrontendStatus(int type, Object value) {
+        mType = type;
+        mValue = value;
+    }
+
+    /** 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) {
+            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;
+    }
+    /**
+     *  Inner Forward Error Correction type as specified in ETSI EN 300 468 V1.15.1
+     *  and ETSI EN 302 307-2 V1.1.1.
+     */
+    @FrontendInnerFec
+    public long getFec() {
+        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_FEC) {
+            throw new IllegalStateException();
+        }
+        return (long) mValue;
+    }
+    /** Modulation */
+    @FrontendModulation
+    public int getModulation() {
+        if (mType != TunerConstants.FRONTEND_STATUS_TYPE_MODULATION) {
+            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;
+    }
+
+    /** Status for each tuning PLPs. */
+    public static class Atsc3PlpInfo {
+        private final int mPlpId;
+        private final boolean mIsLock;
+        private final int mUec;
+
+        private Atsc3PlpInfo(int plpId, boolean isLock, int uec) {
+            mPlpId = plpId;
+            mIsLock = isLock;
+            mUec = uec;
+        }
+
+        /** Gets PLP IDs. */
+        public int getPlpId() {
+            return mPlpId;
+        }
+        /** Gets Demod Lock/Unlock status of this particular PLP. */
+        public boolean getIsLock() {
+            return mIsLock;
+        }
+        /**
+         * 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/ScanMessage.java b/media/java/android/media/tv/tuner/ScanMessage.java
new file mode 100644
index 0000000..35f54f8
--- /dev/null
+++ b/media/java/android/media/tv/tuner/ScanMessage.java
@@ -0,0 +1,131 @@
+/*
+ * 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 62d069d..0ac0c72 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -24,6 +24,7 @@
 import android.media.tv.tuner.TunerConstants.DemuxPidType;
 import android.media.tv.tuner.TunerConstants.FilterSubtype;
 import android.media.tv.tuner.TunerConstants.FilterType;
+import android.media.tv.tuner.TunerConstants.FrontendScanType;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -100,9 +101,10 @@
     private native Frontend nativeOpenFrontendById(int id);
     private native int nativeTune(int type, FrontendSettings settings);
     private native int nativeStopTune();
+    private native int nativeScan(int settingsType, FrontendSettings settings, int scanType);
     private native int nativeSetLnb(int lnbId);
     private native int nativeSetLna(boolean enable);
-
+    private native FrontendStatus[] nativeGetFrontendStatus(int[] statusTypes);
     private native Filter nativeOpenFilter(int type, int subType, int bufferSize);
 
     private native List<Integer> nativeGetLnbIds();
@@ -123,6 +125,12 @@
          * Invoked when there is a frontend event.
          */
         void onEvent(int frontendEventType);
+
+        /**
+         * Invoked when there is a scan message.
+         * @param msg
+         */
+        void onScanMessage(ScanMessage msg);
     }
 
     /**
@@ -144,6 +152,10 @@
      */
     public interface FilterCallback {
         /**
+         * Invoked when there are filter events.
+         */
+        void onFilterEvent(FilterEvent[] events);
+        /**
          * Invoked when filter status changed.
          */
         void onFilterStatus(int status);
@@ -266,6 +278,14 @@
     }
 
     /**
+     * Scan channels.
+     * @hide
+     */
+    public int scan(@NonNull FrontendSettings settings, @FrontendScanType int scanType) {
+        return nativeScan(settings.getType(), settings, scanType);
+    }
+
+    /**
      * Sets Low-Noise Block downconverter (LNB) for satellite frontend.
      *
      * This assigns a hardware LNB resource to the satellite tuner. It can be
@@ -292,6 +312,21 @@
         return nativeSetLna(enable);
     }
 
+    /**
+     * Gets the statuses of the frontend.
+     *
+     * 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
+     */
+    public FrontendStatus[] getFrontendStatus(int[] statusTypes) {
+        return nativeGetFrontendStatus(statusTypes);
+    }
+
     private List<Integer> getFrontendIds() {
         mFrontendIds = nativeGetFrontendIds();
         return mFrontendIds;
@@ -424,26 +459,81 @@
      * <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 boolean nativeAddPid(int pidType, int pid, Filter filter);
-        private native boolean nativeRemovePid(int pidType, int pid, Filter filter);
+        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() {}
 
-        /** @hide */
-        public boolean addPid(@DemuxPidType int pidType, int pid, Filter filter) {
+        /**
+         * 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);
         }
 
-        /** @hide */
-        public boolean removePid(@DemuxPidType int pidType, int pid, Filter 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();
+        }
+
     }
 
     /**
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index 4973b05..e79737a 100644
--- a/media/java/android/media/tv/tuner/TunerConstants.java
+++ b/media/java/android/media/tv/tuner/TunerConstants.java
@@ -17,6 +17,7 @@
 package android.media.tv.tuner;
 
 import android.annotation.IntDef;
+import android.annotation.LongDef;
 import android.hardware.tv.tuner.V1_0.Constants;
 
 import java.lang.annotation.Retention;
@@ -75,6 +76,7 @@
     public static final int DEMUX_T_PID = 1;
     public static final int DEMUX_MMPT_PID = 2;
 
+    @Retention(RetentionPolicy.SOURCE)
     @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})
@@ -127,12 +129,483 @@
     public static final int FILTER_SUBTYPE_PTP = 16;
 
     @Retention(RetentionPolicy.SOURCE)
+    @IntDef({FRONTEND_SCAN_UNDEFINED, FRONTEND_SCAN_AUTO, FRONTEND_SCAN_BLIND})
+    public @interface FrontendScanType {}
+
+    public static final int FRONTEND_SCAN_UNDEFINED = Constants.FrontendScanType.SCAN_UNDEFINED;
+    public static final int FRONTEND_SCAN_AUTO = Constants.FrontendScanType.SCAN_AUTO;
+    public static final int FRONTEND_SCAN_BLIND = Constants.FrontendScanType.SCAN_BLIND;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @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})
+    public @interface ScanMessageType {}
+
+    public static final int SCAN_MESSAGE_TYPE_LOCKED = Constants.FrontendScanMessageType.LOCKED;
+    public static final int SCAN_MESSAGE_TYPE_END = Constants.FrontendScanMessageType.END;
+    public static final int SCAN_MESSAGE_TYPE_PROGRESS_PERCENT =
+            Constants.FrontendScanMessageType.PROGRESS_PERCENT;
+    public static final int SCAN_MESSAGE_TYPE_FREQUENCY =
+            Constants.FrontendScanMessageType.FREQUENCY;
+    public static final int SCAN_MESSAGE_TYPE_SYMBOL_RATE =
+            Constants.FrontendScanMessageType.SYMBOL_RATE;
+    public static final int SCAN_MESSAGE_TYPE_PLP_IDS = Constants.FrontendScanMessageType.PLP_IDS;
+    public static final int SCAN_MESSAGE_TYPE_GROUP_IDS =
+            Constants.FrontendScanMessageType.GROUP_IDS;
+    public static final int SCAN_MESSAGE_TYPE_INPUT_STREAM_IDS =
+            Constants.FrontendScanMessageType.INPUT_STREAM_IDS;
+    public static final int SCAN_MESSAGE_TYPE_STANDARD =
+            Constants.FrontendScanMessageType.STANDARD;
+    public static final int SCAN_MESSAGE_TYPE_ATSC3_PLP_INFO =
+            Constants.FrontendScanMessageType.ATSC3_PLP_INFO;
+
+
+    @Retention(RetentionPolicy.SOURCE)
+    @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})
+    public @interface FrontendStatusType {}
+
+    /**
+     * 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.
+     */
+    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;
+    /**
+     * Moduration 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 ATSC3.0 frontend.
+     */
+    public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO =
+            Constants.FrontendStatusType.ATSC3_PLP_INFO;
+
+
+    @Retention(RetentionPolicy.SOURCE)
+    @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})
+    public @interface FrontendInnerFec {}
+
+    /**
+     * 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;
+
+
+    @Retention(RetentionPolicy.SOURCE)
+    @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})
+    public @interface FrontendModulation {}
+
+    public static final int DVBC_MODULATION_UNDEFINED = Constants.FrontendDvbcModulation.UNDEFINED;
+    public static final int DVBC_MODULATION_AUTO = Constants.FrontendDvbcModulation.AUTO;
+    public static final int DVBC_MODULATION_MOD_16QAM = Constants.FrontendDvbcModulation.MOD_16QAM;
+    public static final int DVBC_MODULATION_MOD_32QAM = Constants.FrontendDvbcModulation.MOD_32QAM;
+    public static final int DVBC_MODULATION_MOD_64QAM = Constants.FrontendDvbcModulation.MOD_64QAM;
+    public static final int DVBC_MODULATION_MOD_128QAM =
+            Constants.FrontendDvbcModulation.MOD_128QAM;
+    public static final int DVBC_MODULATION_MOD_256QAM =
+            Constants.FrontendDvbcModulation.MOD_256QAM;
+    public static final int DVBS_MODULATION_UNDEFINED = Constants.FrontendDvbsModulation.UNDEFINED;
+    public static final int DVBS_MODULATION_AUTO = Constants.FrontendDvbsModulation.AUTO;
+    public static final int DVBS_MODULATION_MOD_QPSK = Constants.FrontendDvbsModulation.MOD_QPSK;
+    public static final int DVBS_MODULATION_MOD_8PSK = Constants.FrontendDvbsModulation.MOD_8PSK;
+    public static final int DVBS_MODULATION_MOD_16QAM = Constants.FrontendDvbsModulation.MOD_16QAM;
+    public static final int DVBS_MODULATION_MOD_16PSK = Constants.FrontendDvbsModulation.MOD_16PSK;
+    public static final int DVBS_MODULATION_MOD_32PSK = Constants.FrontendDvbsModulation.MOD_32PSK;
+    public static final int DVBS_MODULATION_MOD_ACM = Constants.FrontendDvbsModulation.MOD_ACM;
+    public static final int DVBS_MODULATION_MOD_8APSK = Constants.FrontendDvbsModulation.MOD_8APSK;
+    public static final int DVBS_MODULATION_MOD_16APSK =
+            Constants.FrontendDvbsModulation.MOD_16APSK;
+    public static final int DVBS_MODULATION_MOD_32APSK =
+            Constants.FrontendDvbsModulation.MOD_32APSK;
+    public static final int DVBS_MODULATION_MOD_64APSK =
+            Constants.FrontendDvbsModulation.MOD_64APSK;
+    public static final int DVBS_MODULATION_MOD_128APSK =
+            Constants.FrontendDvbsModulation.MOD_128APSK;
+    public static final int DVBS_MODULATION_MOD_256APSK =
+            Constants.FrontendDvbsModulation.MOD_256APSK;
+    public static final int DVBS_MODULATION_MOD_RESERVED =
+            Constants.FrontendDvbsModulation.MOD_RESERVED;
+    public static final int ISDBS_MODULATION_UNDEFINED =
+            Constants.FrontendIsdbsModulation.UNDEFINED;
+    public static final int ISDBS_MODULATION_AUTO = Constants.FrontendIsdbsModulation.AUTO;
+    public static final int ISDBS_MODULATION_MOD_BPSK = Constants.FrontendIsdbsModulation.MOD_BPSK;
+    public static final int ISDBS_MODULATION_MOD_QPSK = Constants.FrontendIsdbsModulation.MOD_QPSK;
+    public static final int ISDBS_MODULATION_MOD_TC8PSK =
+            Constants.FrontendIsdbsModulation.MOD_TC8PSK;
+    public static final int ISDBS3_MODULATION_UNDEFINED =
+            Constants.FrontendIsdbs3Modulation.UNDEFINED;
+    public static final int ISDBS3_MODULATION_AUTO = Constants.FrontendIsdbs3Modulation.AUTO;
+    public static final int ISDBS3_MODULATION_MOD_BPSK =
+            Constants.FrontendIsdbs3Modulation.MOD_BPSK;
+    public static final int ISDBS3_MODULATION_MOD_QPSK =
+            Constants.FrontendIsdbs3Modulation.MOD_QPSK;
+    public static final int ISDBS3_MODULATION_MOD_8PSK =
+            Constants.FrontendIsdbs3Modulation.MOD_8PSK;
+    public static final int ISDBS3_MODULATION_MOD_16APSK =
+            Constants.FrontendIsdbs3Modulation.MOD_16APSK;
+    public static final int ISDBS3_MODULATION_MOD_32APSK =
+            Constants.FrontendIsdbs3Modulation.MOD_32APSK;
+    public static final int ISDBT_MODULATION_UNDEFINED =
+            Constants.FrontendIsdbtModulation.UNDEFINED;
+    public static final int ISDBT_MODULATION_AUTO = Constants.FrontendIsdbtModulation.AUTO;
+    public static final int ISDBT_MODULATION_MOD_DQPSK =
+            Constants.FrontendIsdbtModulation.MOD_DQPSK;
+    public static final int ISDBT_MODULATION_MOD_QPSK = Constants.FrontendIsdbtModulation.MOD_QPSK;
+    public static final int ISDBT_MODULATION_MOD_16QAM =
+            Constants.FrontendIsdbtModulation.MOD_16QAM;
+    public static final int ISDBT_MODULATION_MOD_64QAM =
+            Constants.FrontendIsdbtModulation.MOD_64QAM;
+
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({SPECTRAL_INVERSION_UNDEFINED, SPECTRAL_INVERSION_NORMAL, SPECTRAL_INVERSION_INVERTED})
+    public @interface FrontendDvbcSpectralInversion {}
+
+    public static final int SPECTRAL_INVERSION_UNDEFINED =
+            Constants.FrontendDvbcSpectralInversion.UNDEFINED;
+    public static final int SPECTRAL_INVERSION_NORMAL =
+            Constants.FrontendDvbcSpectralInversion.NORMAL;
+    public static final int SPECTRAL_INVERSION_INVERTED =
+            Constants.FrontendDvbcSpectralInversion.INVERTED;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({LNB_VOLTAGE_NONE, LNB_VOLTAGE_VOLTAGE_5V, LNB_VOLTAGE_VOLTAGE_11V,
+            LNB_VOLTAGE_VOLTAGE_12V, LNB_VOLTAGE_VOLTAGE_13V, LNB_VOLTAGE_VOLTAGE_14V,
+            LNB_VOLTAGE_VOLTAGE_15V, LNB_VOLTAGE_VOLTAGE_18V})
+    public @interface LnbVoltage {}
+
+    public static final int LNB_VOLTAGE_NONE = Constants.LnbVoltage.NONE;
+    public static final int LNB_VOLTAGE_VOLTAGE_5V = Constants.LnbVoltage.VOLTAGE_5V;
+    public static final int LNB_VOLTAGE_VOLTAGE_11V = Constants.LnbVoltage.VOLTAGE_11V;
+    public static final int LNB_VOLTAGE_VOLTAGE_12V = Constants.LnbVoltage.VOLTAGE_12V;
+    public static final int LNB_VOLTAGE_VOLTAGE_13V = Constants.LnbVoltage.VOLTAGE_13V;
+    public static final int LNB_VOLTAGE_VOLTAGE_14V = Constants.LnbVoltage.VOLTAGE_14V;
+    public static final int LNB_VOLTAGE_VOLTAGE_15V = Constants.LnbVoltage.VOLTAGE_15V;
+    public static final int LNB_VOLTAGE_VOLTAGE_18V = Constants.LnbVoltage.VOLTAGE_18V;
+
+
+    @Retention(RetentionPolicy.SOURCE)
+    @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})
+    public @interface FrontendDvbtHierarchy {}
+
+    public static final int HIERARCHY_UNDEFINED = Constants.FrontendDvbtHierarchy.UNDEFINED;
+    public static final int HIERARCHY_AUTO = Constants.FrontendDvbtHierarchy.AUTO;
+    public static final int HIERARCHY_NON_NATIVE =
+            Constants.FrontendDvbtHierarchy.HIERARCHY_NON_NATIVE;
+    public static final int HIERARCHY_1_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_1_NATIVE;
+    public static final int HIERARCHY_2_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_2_NATIVE;
+    public static final int HIERARCHY_4_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_4_NATIVE;
+    public static final int HIERARCHY_NON_INDEPTH =
+            Constants.FrontendDvbtHierarchy.HIERARCHY_NON_INDEPTH;
+    public static final int HIERARCHY_1_INDEPTH =
+            Constants.FrontendDvbtHierarchy.HIERARCHY_1_INDEPTH;
+    public static final int HIERARCHY_2_INDEPTH =
+            Constants.FrontendDvbtHierarchy.HIERARCHY_2_INDEPTH;
+    public static final int HIERARCHY_4_INDEPTH =
+            Constants.FrontendDvbtHierarchy.HIERARCHY_4_INDEPTH;
+
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({FILTER_SETTINGS_TS, FILTER_SETTINGS_MMTP, FILTER_SETTINGS_IP, FILTER_SETTINGS_TLV,
+            FILTER_SETTINGS_ALP})
+    public @interface FilterSettingsType {}
+
+    public static final int FILTER_SETTINGS_TS = Constants.DemuxFilterMainType.TS;
+    public static final int FILTER_SETTINGS_MMTP = Constants.DemuxFilterMainType.MMTP;
+    public static final int FILTER_SETTINGS_IP = Constants.DemuxFilterMainType.IP;
+    public static final int FILTER_SETTINGS_TLV = Constants.DemuxFilterMainType.TLV;
+    public static final int FILTER_SETTINGS_ALP = Constants.DemuxFilterMainType.ALP;
+
+    @Retention(RetentionPolicy.SOURCE)
     @IntDef({DVR_SETTINGS_RECORD, DVR_SETTINGS_PLAYBACK})
     public @interface DvrSettingsType {}
 
     public static final int DVR_SETTINGS_RECORD = Constants.DvrType.RECORD;
     public static final int DVR_SETTINGS_PLAYBACK = Constants.DvrType.PLAYBACK;
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({RESULT_SUCCESS, RESULT_UNAVAILABLE, RESULT_NOT_INITIALIZED, RESULT_INVALID_STATE,
+            RESULT_INVALID_ARGUMENT, RESULT_OUT_OF_MEMORY, RESULT_UNKNOWN_ERROR})
+    public @interface Result {}
+
+    public static final int RESULT_SUCCESS = Constants.Result.SUCCESS;
+    public static final int RESULT_UNAVAILABLE = Constants.Result.UNAVAILABLE;
+    public static final int RESULT_NOT_INITIALIZED = Constants.Result.NOT_INITIALIZED;
+    public static final int RESULT_INVALID_STATE = Constants.Result.INVALID_STATE;
+    public static final int RESULT_INVALID_ARGUMENT = Constants.Result.INVALID_ARGUMENT;
+    public static final int RESULT_OUT_OF_MEMORY = Constants.Result.OUT_OF_MEMORY;
+    public static final int RESULT_UNKNOWN_ERROR = Constants.Result.UNKNOWN_ERROR;
+
     private TunerConstants() {
     }
 }
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 4a2baed..3fd14de 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -312,6 +312,15 @@
     return (int)result;
 }
 
+int JTuner::scan(const FrontendSettings& settings, FrontendScanType scanType) {
+    if (mFe == NULL) {
+        ALOGE("frontend is not initialized");
+        return (int)Result::INVALID_STATE;
+    }
+    Result result = mFe->scan(settings, scanType);
+    return (int)result;
+}
+
 bool JTuner::openDemux() {
     if (mTuner == nullptr) {
         return false;
@@ -604,6 +613,13 @@
     return 0;
 }
 
+static int android_media_tv_Tuner_scan(
+        JNIEnv *env, jobject thiz, jint settingsType, jobject settings, jint scanType) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->scan(getFrontendSettings(
+            env, settingsType, settings), static_cast<FrontendScanType>(scanType));
+}
+
 static int android_media_tv_Tuner_set_lnb(JNIEnv, jobject, jint) {
     return 0;
 }
@@ -612,6 +628,10 @@
     return 0;
 }
 
+static jobjectArray android_media_tv_Tuner_get_frontend_status(JNIEnv, jobject, jintArray) {
+    return NULL;
+}
+
 static jobject android_media_tv_Tuner_get_lnb_ids(JNIEnv *env, jobject thiz) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->getLnbIds();
@@ -769,7 +789,7 @@
     return tuner->openDescrambler();
 }
 
-static bool android_media_tv_Tuner_add_pid(
+static int android_media_tv_Tuner_add_pid(
         JNIEnv *env, jobject descrambler, jint pidType, jint pid, jobject filter) {
     sp<IDescrambler> descramblerSp = getDescrambler(env, descrambler);
     if (descramblerSp == NULL) {
@@ -777,10 +797,10 @@
     }
     sp<IFilter> filterSp = getFilter(env, filter)->getIFilter();
     Result result = descramblerSp->addPid(getDemuxPid((int)pidType, (int)pid), filterSp);
-    return result == Result::SUCCESS;
+    return (int)result;
 }
 
-static bool android_media_tv_Tuner_remove_pid(
+static int android_media_tv_Tuner_remove_pid(
         JNIEnv *env, jobject descrambler, jint pidType, jint pid, jobject filter) {
     sp<IDescrambler> descramblerSp = getDescrambler(env, descrambler);
     if (descramblerSp == NULL) {
@@ -788,7 +808,15 @@
     }
     sp<IFilter> filterSp = getFilter(env, filter)->getIFilter();
     Result result = descramblerSp->removePid(getDemuxPid((int)pidType, (int)pid), filterSp);
-    return result == Result::SUCCESS;
+    return (int)result;
+}
+
+static int android_media_tv_Tuner_set_key_token(JNIEnv, jobject, jbyteArray) {
+    return 0;
+}
+
+static int android_media_tv_Tuner_close_descrambler(JNIEnv, jobject) {
+    return 0;
 }
 
 static jobject android_media_tv_Tuner_open_dvr(JNIEnv *env, jobject thiz, jint type, jint bufferSize) {
@@ -863,8 +891,12 @@
     { "nativeTune", "(ILandroid/media/tv/tuner/FrontendSettings;)I",
             (void *)android_media_tv_Tuner_tune },
     { "nativeStopTune", "()I", (void *)android_media_tv_Tuner_stop_tune },
+    { "nativeScan", "(ILandroid/media/tv/tuner/FrontendSettings;I)I",
+            (void *)android_media_tv_Tuner_scan },
     { "nativeSetLnb", "(I)I", (void *)android_media_tv_Tuner_set_lnb },
     { "nativeSetLna", "(Z)I", (void *)android_media_tv_Tuner_set_lna },
+    { "nativeGetFrontendStatus", "([I)[Landroid/media/tv/tuner/FrontendStatus;",
+            (void *)android_media_tv_Tuner_get_frontend_status },
     { "nativeOpenFilter", "(III)Landroid/media/tv/tuner/Tuner$Filter;",
             (void *)android_media_tv_Tuner_open_filter },
     { "nativeGetLnbIds", "()Ljava/util/List;",
@@ -887,10 +919,12 @@
 };
 
 static const JNINativeMethod gDescramblerMethods[] = {
-    { "nativeAddPid", "(IILandroid/media/tv/tuner/Tuner$Filter;)Z",
+    { "nativeAddPid", "(IILandroid/media/tv/tuner/Tuner$Filter;)I",
             (void *)android_media_tv_Tuner_add_pid },
-    { "nativeRemovePid", "(IILandroid/media/tv/tuner/Tuner$Filter;)Z",
+    { "nativeRemovePid", "(IILandroid/media/tv/tuner/Tuner$Filter;)I",
             (void *)android_media_tv_Tuner_remove_pid },
+    { "nativeSetKeyToken", "([B)I", (void *)android_media_tv_Tuner_set_key_token },
+    { "nativeClose", "()V", (void *)android_media_tv_Tuner_close_descrambler },
 };
 
 static const JNINativeMethod gDvrMethods[] = {
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 9f9fb27..d37a2d9 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -39,6 +39,7 @@
 using ::android::hardware::tv::tuner::V1_0::FrontendId;
 using ::android::hardware::tv::tuner::V1_0::FrontendScanMessage;
 using ::android::hardware::tv::tuner::V1_0::FrontendScanMessageType;
+using ::android::hardware::tv::tuner::V1_0::FrontendScanType;
 using ::android::hardware::tv::tuner::V1_0::FrontendSettings;
 using ::android::hardware::tv::tuner::V1_0::IDemux;
 using ::android::hardware::tv::tuner::V1_0::IDescrambler;
@@ -122,6 +123,7 @@
     jobject getFrontendIds();
     jobject openFrontendById(int id);
     int tune(const FrontendSettings& settings);
+    int scan(const FrontendSettings& settings, FrontendScanType scanType);
     jobject getLnbIds();
     jobject openLnbById(int id);
     jobject openFilter(DemuxFilterType type, int bufferSize);
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 6650f96..d04c9b2 100644
--- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
@@ -16,12 +16,17 @@
 
 package com.android.mediarouteprovider.example;
 
+import static android.media.MediaRoute2Info.DEVICE_TYPE_SPEAKER;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_TV;
+
 import android.content.Intent;
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2ProviderService;
+import android.media.RouteSessionInfo;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.text.TextUtils;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -33,6 +38,9 @@
     public static final String ROUTE_NAME1 = "Sample Route 1";
     public static final String ROUTE_ID2 = "route_id2";
     public static final String ROUTE_NAME2 = "Sample Route 2";
+    public static final String ROUTE_ID3_SESSION_CREATION_FAILED =
+            "route_id3_session_creation_failed";
+    public static final String ROUTE_NAME3 = "Sample Route 3 - Session creation failed";
 
     public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
     public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
@@ -51,14 +59,22 @@
     public static final String CATEGORY_SPECIAL =
             "com.android.mediarouteprovider.CATEGORY_SPECIAL";
 
+    public static final int SESSION_ID_1 = 1000;
+
     Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
 
     private void initializeRoutes() {
         MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1)
                 .addSupportedCategory(CATEGORY_SAMPLE)
+                .setDeviceType(DEVICE_TYPE_TV)
                 .build();
         MediaRoute2Info route2 = new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2)
                 .addSupportedCategory(CATEGORY_SAMPLE)
+                .setDeviceType(DEVICE_TYPE_SPEAKER)
+                .build();
+        MediaRoute2Info route3 = new MediaRoute2Info.Builder(
+                ROUTE_ID3_SESSION_CREATION_FAILED, ROUTE_NAME3)
+                .addSupportedCategory(CATEGORY_SAMPLE)
                 .build();
         MediaRoute2Info routeSpecial =
                 new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_CATEGORY, ROUTE_NAME_SPECIAL_CATEGORY)
@@ -79,6 +95,7 @@
 
         mRoutes.put(route1.getId(), route1);
         mRoutes.put(route2.getId(), route2);
+        mRoutes.put(route3.getId(), route3);
         mRoutes.put(routeSpecial.getId(), routeSpecial);
         mRoutes.put(fixedVolumeRoute.getId(), fixedVolumeRoute);
         mRoutes.put(variableVolumeRoute.getId(), variableVolumeRoute);
@@ -122,7 +139,8 @@
 
     @Override
     public void onControlRequest(String routeId, Intent request) {
-        if (ACTION_REMOVE_ROUTE.equals(request.getAction())) {
+        String action = request.getAction();
+        if (ACTION_REMOVE_ROUTE.equals(action)) {
             MediaRoute2Info route = mRoutes.get(routeId);
             if (route != null) {
                 mRoutes.remove(routeId);
@@ -159,10 +177,61 @@
         publishRoutes();
     }
 
+    @Override
+    public void onCreateSession(String packageName, String routeId, String controlCategory,
+            int requestId) {
+        if (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);
+            return;
+        }
+
+        RouteSessionInfo sessionInfo = new RouteSessionInfo.Builder(
+                SESSION_ID_1, packageName, controlCategory)
+                .addSelectedRoute(routeId)
+                .build();
+        notifySessionCreated(sessionInfo,  requestId);
+    }
+
+    @Override
+    public void onDestroySession(int sessionId, RouteSessionInfo lastSessionInfo) {}
+
+    @Override
+    public void onAddRoute(int sessionId, String routeId) {
+        RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+        //TODO: we may want to remove route if it belongs to another session
+        RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+                .addSelectedRoute(routeId)
+                .build();
+        updateSessionInfo(newSessionInfo);
+        publishRoutes();
+    }
+
+    @Override
+    public void onRemoveRoute(int sessionId, String routeId) {
+        RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+        RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+                .removeSelectedRoute(routeId)
+                .build();
+        updateSessionInfo(newSessionInfo);
+        publishRoutes();
+    }
+
+    @Override
+    public void onTransferRoute(int sessionId, String routeId) {
+        RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+        RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+                .clearSelectedRoutes()
+                .addSelectedRoute(routeId)
+                .build();
+        updateSessionInfo(newSessionInfo);
+        publishRoutes();
+    }
+
     void publishRoutes() {
         MediaRoute2ProviderInfo info = new MediaRoute2ProviderInfo.Builder()
                 .addRoutes(mRoutes.values())
                 .build();
-        setProviderInfo(info);
+        updateProviderInfo(info);
     }
 }
diff --git a/media/tests/MediaRouter/Android.bp b/media/tests/MediaRouter/Android.bp
index 611b25a..5a0a50c 100644
--- a/media/tests/MediaRouter/Android.bp
+++ b/media/tests/MediaRouter/Android.bp
@@ -11,6 +11,7 @@
     static_libs: [
         "android-support-test",
         "mockito-target-minus-junit4",
+        "testng"
     ],
 
     platform_apis: true,
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
index 3266285..8fe28d9 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
@@ -16,19 +16,37 @@
 
 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.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.ROUTE_ID1;
+import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID3_SESSION_CREATION_FAILED;
 import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_CATEGORY;
 import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_VARIABLE_VOLUME;
 import static com.android.mediaroutertest.MediaRouterManagerTest.SYSTEM_PROVIDER_ID;
 
 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.testng.Assert.assertThrows;
 
 import android.content.Context;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2;
+import android.media.MediaRouter2.SessionCreationCallback;
+import android.media.RouteSessionController;
+import android.net.Uri;
+import android.os.Parcel;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -39,6 +57,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -69,13 +88,6 @@
     public void tearDown() throws Exception {
     }
 
-    @Test
-    public void testGetSelectedRoute_afterCreation() throws Exception {
-        MediaRouter2 router = MediaRouter2.getInstance(mContext);
-        MediaRoute2Info initiallySelectedRoute = router.getSelectedRoute();
-        assertNotNull(initiallySelectedRoute);
-    }
-
     /**
      * Tests if we get proper routes for application that has special control category.
      */
@@ -88,6 +100,90 @@
     }
 
     @Test
+    public void testRouteInfoEquality() {
+        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder("id", "name")
+                .setDescription("description")
+                .setClientPackageName("com.android.mediaroutertest")
+                .setConnectionState(CONNECTION_STATE_CONNECTING)
+                .setIconUri(new Uri.Builder().path("icon").build())
+                .setVolume(5)
+                .setVolumeMax(20)
+                .addSupportedCategory(CATEGORY_SAMPLE)
+                .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
+                .setDeviceType(DEVICE_TYPE_SPEAKER)
+                .build();
+
+        MediaRoute2Info routeInfoRebuilt = new MediaRoute2Info.Builder(routeInfo).build();
+        assertEquals(routeInfo, routeInfoRebuilt);
+
+        Parcel parcel = Parcel.obtain();
+        parcel.writeParcelable(routeInfo, 0);
+        parcel.setDataPosition(0);
+        MediaRoute2Info routeInfoFromParcel = parcel.readParcelable(null);
+
+        assertEquals(routeInfo, routeInfoFromParcel);
+    }
+
+    @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);
 
@@ -108,6 +204,109 @@
                 (route -> route.getVolume() == originalVolume));
     }
 
+    @Test
+    public void testRequestCreateSessionWithInvalidArguments() {
+        MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build();
+        String controlCategory = "controlCategory";
+        Executor executor = mExecutor;
+        MediaRouter2.SessionCreationCallback callback = new MediaRouter2.SessionCreationCallback();
+
+        // Tests null route
+        assertThrows(NullPointerException.class,
+                () -> mRouter2.requestCreateSession(null, controlCategory, executor, callback));
+
+        // Tests null or empty control category
+        assertThrows(IllegalArgumentException.class,
+                () -> mRouter2.requestCreateSession(route, null, executor, callback));
+        assertThrows(IllegalArgumentException.class,
+                () -> mRouter2.requestCreateSession(route, "", executor, callback));
+
+        // Tests null executor
+        assertThrows(NullPointerException.class,
+                () -> mRouter2.requestCreateSession(route, controlCategory, null, callback));
+
+        // Tests null callback
+        assertThrows(NullPointerException.class,
+                () -> mRouter2.requestCreateSession(route, controlCategory, executor, null));
+    }
+
+    @Test
+    public void testRequestCreateSessionSuccess() throws Exception {
+        final List<String> sampleControlCategory = new ArrayList<>();
+        sampleControlCategory.add(CATEGORY_SAMPLE);
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+
+        // Create session with this route
+        SessionCreationCallback callback = new SessionCreationCallback() {
+            @Override
+            public void onSessionCreated(RouteSessionController controller) {
+                assertNotNull(controller);
+                assertTrue(controller.getSelectedRoutes().contains(ROUTE_ID1));
+                assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getCategory()));
+                successLatch.countDown();
+            }
+
+            @Override
+            public void onSessionCreationFailed() {
+                failureLatch.countDown();
+            }
+        };
+
+        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
+        mRouter2.registerCallback(mExecutor, new MediaRouter2.RouteCallback());
+
+        mRouter2.requestCreateSession(route, CATEGORY_SAMPLE, mExecutor, callback);
+        assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        // onSessionCreationFailed should not be called.
+        assertFalse(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testRequestCreateSessionFailure() throws Exception {
+        final List<String> sampleControlCategory = new ArrayList<>();
+        sampleControlCategory.add(CATEGORY_SAMPLE);
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+        MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED);
+        assertNotNull(route);
+
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+
+        // Create session with this route
+        SessionCreationCallback callback = new SessionCreationCallback() {
+            @Override
+            public void onSessionCreated(RouteSessionController controller) {
+                successLatch.countDown();
+            }
+
+            @Override
+            public void onSessionCreationFailed() {
+                failureLatch.countDown();
+            }
+        };
+
+        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
+        mRouter2.registerCallback(mExecutor, new MediaRouter2.RouteCallback());
+
+        mRouter2.requestCreateSession(route, CATEGORY_SAMPLE, mExecutor, callback);
+        assertTrue(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        // onSessionCreated should not be called.
+        assertFalse(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testRequestCreateSessionMultipleSessions() throws Exception {
+        // TODO: Test creating multiple sessions (Check the ID of each controller)
+    }
 
     // Helper for getting routes easily
     static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
@@ -124,7 +323,8 @@
         CountDownLatch latch = new CountDownLatch(1);
 
         // A dummy callback is required to send control category info.
-        MediaRouter2.Callback routerCallback = new MediaRouter2.Callback() {
+        MediaRouter2.RouteCallback
+                routeCallback = new MediaRouter2.RouteCallback() {
             @Override
             public void onRoutesAdded(List<MediaRoute2Info> routes) {
                 for (int i = 0; i < routes.size(); i++) {
@@ -137,19 +337,20 @@
         };
 
         mRouter2.setControlCategories(controlCategories);
-        mRouter2.registerCallback(mExecutor, routerCallback);
+        mRouter2.registerCallback(mExecutor, routeCallback);
         try {
             latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
             return createRouteMap(mRouter2.getRoutes());
         } finally {
-            mRouter2.unregisterCallback(routerCallback);
+            mRouter2.unregisterCallback(routeCallback);
         }
     }
 
     void awaitOnRouteChanged(Runnable task, String routeId,
             Predicate<MediaRoute2Info> predicate) throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
-        MediaRouter2.Callback callback = new MediaRouter2.Callback() {
+        MediaRouter2.RouteCallback
+                routeCallback = new MediaRouter2.RouteCallback() {
             @Override
             public void onRoutesChanged(List<MediaRoute2Info> changed) {
                 MediaRoute2Info route = createRouteMap(changed).get(routeId);
@@ -158,12 +359,12 @@
                 }
             }
         };
-        mRouter2.registerCallback(mExecutor, callback);
+        mRouter2.registerCallback(mExecutor, routeCallback);
         try {
             task.run();
             assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         } finally {
-            mRouter2.unregisterCallback(callback);
+            mRouter2.unregisterCallback(routeCallback);
         }
     }
 }
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index 2772aa4..9761e6d 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -28,8 +28,8 @@
 import android.content.Intent;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2;
+import android.media.MediaRouter2.RouteCallback;
 import android.media.MediaRouter2Manager;
-import android.os.Bundle;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -60,6 +60,9 @@
     public static final String ROUTE_NAME1 = "Sample Route 1";
     public static final String ROUTE_ID2 = "route_id2";
     public static final String ROUTE_NAME2 = "Sample Route 2";
+    public static final String ROUTE_ID3_SESSION_CREATION_FAILED =
+            "route_id3_session_creation_failed";
+    public static final String ROUTE_NAME3 = "Sample Route 3 - Session creation failed";
 
     public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
     public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
@@ -92,7 +95,8 @@
     private String mPackageName;
 
     private final List<MediaRouter2Manager.Callback> mManagerCallbacks = new ArrayList<>();
-    private final List<MediaRouter2.Callback> mRouterCallbacks = new ArrayList<>();
+    private final List<RouteCallback> mRouteCallbacks =
+            new ArrayList<>();
 
     public static final List<String> CATEGORIES_ALL = new ArrayList();
     public static final List<String> CATEGORIES_SPECIAL = new ArrayList();
@@ -164,7 +168,7 @@
         CountDownLatch latch = new CountDownLatch(1);
         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
 
-        addRouterCallback(new MediaRouter2.Callback());
+        addRouterCallback(new RouteCallback());
         addManagerCallback(new MediaRouter2Manager.Callback() {
             @Override
             public void onRoutesRemoved(List<MediaRoute2Info> routes) {
@@ -196,29 +200,30 @@
 
     /**
      * Tests if MR2.Callback.onRouteSelected is called when a route is selected from MR2Manager.
+     *
+     * TODO: Change this test so that this test check whether the route is added in a session.
+     *       Until then, temporailiy removing @Test annotation.
      */
-    @Test
     public void testRouterOnRouteSelected() throws Exception {
         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
 
         CountDownLatch latch = new CountDownLatch(1);
 
         addManagerCallback(new MediaRouter2Manager.Callback());
-        addRouterCallback(new MediaRouter2.Callback() {
-            @Override
-            public void onRouteSelected(MediaRoute2Info route, int reason, Bundle controlHints) {
-                if (route != null && TextUtils.equals(route.getId(), ROUTE_ID1)) {
-                    latch.countDown();
-                }
-            }
-        });
+//        addRouterCallback(new RouteDiscoveryCallback() {
+//            @Override
+//            public void onRouteSelected(MediaRoute2Info route, int reason, Bundle controlHints) {
+//                if (route != null && TextUtils.equals(route.getId(), 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 {
             mManager.unselectRoute(mPackageName);
@@ -234,7 +239,7 @@
         CountDownLatch latch = new CountDownLatch(1);
         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
 
-        addRouterCallback(new MediaRouter2.Callback());
+        addRouterCallback(new RouteCallback());
         addManagerCallback(new MediaRouter2Manager.Callback() {
             @Override
             public void onRouteSelected(String packageName, MediaRoute2Info route) {
@@ -262,7 +267,7 @@
         CountDownLatch latch2 = new CountDownLatch(1);
 
         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
-        addRouterCallback(new MediaRouter2.Callback());
+        addRouterCallback(new RouteCallback());
         addManagerCallback(new MediaRouter2Manager.Callback() {
             @Override
             public void onRouteSelected(String packageName, MediaRoute2Info route) {
@@ -293,7 +298,7 @@
     @Test
     public void testSingleProviderSelect() throws Exception {
         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
-        addRouterCallback(new MediaRouter2.Callback());
+        addRouterCallback(new RouteCallback());
 
         awaitOnRouteChangedManager(
                 () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)),
@@ -347,7 +352,7 @@
         CountDownLatch latch = new CountDownLatch(2);
 
         // A dummy callback is required to send control category info.
-        MediaRouter2.Callback routerCallback = new MediaRouter2.Callback();
+        RouteCallback routeCallback = new RouteCallback();
         MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
             @Override
             public void onRoutesAdded(List<MediaRoute2Info> routes) {
@@ -362,19 +367,20 @@
 
             @Override
             public void onControlCategoriesChanged(String packageName, List<String> categories) {
-                if (TextUtils.equals(mPackageName, packageName)) {
+                if (TextUtils.equals(mPackageName, packageName)
+                        && controlCategories.equals(categories)) {
                     latch.countDown();
                 }
             }
         };
         mManager.registerCallback(mExecutor, managerCallback);
         mRouter2.setControlCategories(controlCategories);
-        mRouter2.registerCallback(mExecutor, routerCallback);
+        mRouter2.registerCallback(mExecutor, routeCallback);
         try {
             latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
             return createRouteMap(mManager.getAvailableRoutes(mPackageName));
         } finally {
-            mRouter2.unregisterCallback(routerCallback);
+            mRouter2.unregisterCallback(routeCallback);
             mManager.unregisterCallback(managerCallback);
         }
     }
@@ -415,9 +421,9 @@
         mManager.registerCallback(mExecutor, callback);
     }
 
-    private void addRouterCallback(MediaRouter2.Callback callback) {
-        mRouterCallbacks.add(callback);
-        mRouter2.registerCallback(mExecutor, callback);
+    private void addRouterCallback(RouteCallback routeCallback) {
+        mRouteCallbacks.add(routeCallback);
+        mRouter2.registerCallback(mExecutor, routeCallback);
     }
 
     private void clearCallbacks() {
@@ -426,9 +432,9 @@
         }
         mManagerCallbacks.clear();
 
-        for (MediaRouter2.Callback callback : mRouterCallbacks) {
-            mRouter2.unregisterCallback(callback);
+        for (RouteCallback routeCallback : mRouteCallbacks) {
+            mRouter2.unregisterCallback(routeCallback);
         }
-        mRouterCallbacks.clear();
+        mRouteCallbacks.clear();
     }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index b862e95..cf4ee7d 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -32,7 +32,7 @@
 import com.android.systemui.power.EnhancedEstimatesImpl;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImplementation;
-import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.stackdivider.DividerModule;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
@@ -52,17 +52,14 @@
 import com.android.systemui.volume.CarVolumeDialogComponent;
 import com.android.systemui.volume.VolumeDialogComponent;
 
-import java.util.Optional;
-
 import javax.inject.Named;
 import javax.inject.Singleton;
 
 import dagger.Binds;
-import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
-@Module
+@Module(includes = {DividerModule.class})
 abstract class CarSystemUIModule {
 
     @Binds
@@ -85,12 +82,6 @@
 
     @Singleton
     @Provides
-    static Divider provideDivider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy) {
-        return new Divider(context, recentsOptionalLazy);
-    }
-
-    @Singleton
-    @Provides
     static HeadsUpManagerPhone provideHeadsUpManagerPhone(Context context,
             StatusBarStateController statusBarStateController,
             KeyguardBypassController bypassController) {
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 1b171e8..5195f1a 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -17,6 +17,7 @@
 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;
 import android.animation.AnimatorListenerAdapter;
@@ -30,6 +31,7 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
 import android.os.PowerManager;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -57,9 +59,9 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.BatteryMeterView;
 import com.android.systemui.Dependency;
+import com.android.systemui.InitController;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
-import com.android.systemui.UiOffloadThread;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
@@ -68,12 +70,14 @@
 import com.android.systemui.car.CarServiceProvider;
 import com.android.systemui.classifier.FalsingLog;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.navigationbar.car.CarNavigationBarController;
+import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.car.CarQSFragment;
@@ -141,6 +145,7 @@
 import java.io.PrintWriter;
 import java.util.Map;
 import java.util.Optional;
+import java.util.concurrent.Executor;
 
 import javax.inject.Named;
 import javax.inject.Provider;
@@ -273,7 +278,7 @@
             NotificationAlertingManager notificationAlertingManager,
             DisplayMetrics displayMetrics,
             MetricsLogger metricsLogger,
-            UiOffloadThread uiOffloadThread,
+            @UiBackground Executor uiBgExecutor,
             NotificationMediaManager notificationMediaManager,
             NotificationLockscreenUserManager lockScreenUserManager,
             NotificationRemoteInputManager remoteInputManager,
@@ -318,6 +323,9 @@
             ShadeController shadeController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
+            InitController initController,
+            DarkIconDispatcher darkIconDispatcher,
+            @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
             DismissCallbackRegistry dismissCallbackRegistry,
             /* Car Settings injected components. */
             CarServiceProvider carServiceProvider,
@@ -353,7 +361,7 @@
                 notificationAlertingManager,
                 displayMetrics,
                 metricsLogger,
-                uiOffloadThread,
+                uiBgExecutor,
                 notificationMediaManager,
                 lockScreenUserManager,
                 remoteInputManager,
@@ -398,6 +406,9 @@
                 superStatusBarViewFactory,
                 statusBarKeyguardViewManager,
                 viewMediatorCallback,
+                initController,
+                darkIconDispatcher,
+                timeTickHandler,
                 dismissCallbackRegistry);
         mScrimController = scrimController;
         mLockscreenLockIconController = lockscreenLockIconController;
@@ -1043,6 +1054,12 @@
         mScrimController.setScrimBehindDrawable(mNotificationPanelBackground);
     }
 
+    @Override
+    public void onLocaleListChanged() {
+        connectNotificationsUI();
+        registerNavBarListeners();
+    }
+
     /**
      * Returns the {@link Drawable} that represents the wallpaper that the user has currently set.
      */
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 7b21d9d..b4323cc 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -17,25 +17,29 @@
 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;
+import android.os.Handler;
 import android.os.PowerManager;
 import android.util.DisplayMetrics;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.UiOffloadThread;
+import com.android.systemui.InitController;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.car.CarServiceProvider;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.navigationbar.car.CarNavigationBarController;
+import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.ScreenPinningRequest;
@@ -96,6 +100,7 @@
 import com.android.systemui.volume.VolumeComponent;
 
 import java.util.Optional;
+import java.util.concurrent.Executor;
 
 import javax.inject.Named;
 import javax.inject.Provider;
@@ -143,7 +148,7 @@
             NotificationAlertingManager notificationAlertingManager,
             DisplayMetrics displayMetrics,
             MetricsLogger metricsLogger,
-            UiOffloadThread uiOffloadThread,
+            @UiBackground Executor uiBgExecutor,
             NotificationMediaManager notificationMediaManager,
             NotificationLockscreenUserManager lockScreenUserManager,
             NotificationRemoteInputManager remoteInputManager,
@@ -188,6 +193,9 @@
             ShadeController shadeController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
+            InitController initController,
+            DarkIconDispatcher darkIconDispatcher,
+            @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
             DismissCallbackRegistry dismissCallbackRegistry,
             CarServiceProvider carServiceProvider,
             Lazy<PowerManagerHelper> powerManagerHelperLazy,
@@ -222,7 +230,7 @@
                 notificationAlertingManager,
                 displayMetrics,
                 metricsLogger,
-                uiOffloadThread,
+                uiBgExecutor,
                 notificationMediaManager,
                 lockScreenUserManager,
                 remoteInputManager,
@@ -266,6 +274,9 @@
                 shadeController,
                 statusBarKeyguardViewManager,
                 viewMediatorCallback,
+                initController,
+                darkIconDispatcher,
+                timeTickHandler,
                 dismissCallbackRegistry,
                 carServiceProvider,
                 powerManagerHelperLazy,
diff --git a/packages/PackageInstaller/res/values-fr/strings.xml b/packages/PackageInstaller/res/values-fr/strings.xml
index b85eb97..462c60e 100644
--- a/packages/PackageInstaller/res/values-fr/strings.xml
+++ b/packages/PackageInstaller/res/values-fr/strings.xml
@@ -62,7 +62,7 @@
     <string name="uninstalling_notification_channel" msgid="840153394325714653">"Désinstallations en cours"</string>
     <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Échec des désinstallations"</string>
     <string name="uninstalling" msgid="8709566347688966845">"Désinstallation…"</string>
-    <string name="uninstalling_app" msgid="8866082646836981397">"Désinstallation du package <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>…"</string>
+    <string name="uninstalling_app" msgid="8866082646836981397">"Désinstallation de <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>…"</string>
     <string name="uninstall_done" msgid="439354138387969269">"Désinstallation terminée."</string>
     <string name="uninstall_done_app" msgid="4588850984473605768">"Le package <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> a été désinstallé"</string>
     <string name="uninstall_failed" msgid="1847750968168364332">"Échec de la désinstallation."</string>
diff --git a/packages/PrintSpooler/res/values-cs/strings.xml b/packages/PrintSpooler/res/values-cs/strings.xml
index 1f38e3c..dd2ce5a 100644
--- a/packages/PrintSpooler/res/values-cs/strings.xml
+++ b/packages/PrintSpooler/res/values-cs/strings.xml
@@ -54,7 +54,7 @@
     <string name="print_search_box_hidden_utterance" msgid="5727755169343113351">"Vyhledávací pole je skryto"</string>
     <string name="print_add_printer" msgid="1088656468360653455">"Přidat tiskárnu"</string>
     <string name="print_select_printer" msgid="7388760939873368698">"Vybrat tiskárnu"</string>
-    <string name="print_forget_printer" msgid="5035287497291910766">"Odstranit tiskárnu"</string>
+    <string name="print_forget_printer" msgid="5035287497291910766">"Zapomenout tiskárnu"</string>
     <plurals name="print_search_result_count_utterance" formatted="false" msgid="6997663738361080868">
       <item quantity="few">Nalezené tiskárny: <xliff:g id="COUNT_1">%1$s</xliff:g></item>
       <item quantity="many">Nalezené tiskárny: <xliff:g id="COUNT_1">%1$s</xliff:g></item>
diff --git a/packages/PrintSpooler/res/values-iw/strings.xml b/packages/PrintSpooler/res/values-iw/strings.xml
index 22ef612..64db711 100644
--- a/packages/PrintSpooler/res/values-iw/strings.xml
+++ b/packages/PrintSpooler/res/values-iw/strings.xml
@@ -108,7 +108,7 @@
   </string-array>
     <string name="print_write_error_message" msgid="5787642615179572543">"לא ניתן היה לכתוב לקובץ"</string>
     <string name="print_error_default_message" msgid="8602678405502922346">"מצטערים, אך זה לא עבד. נסה שוב."</string>
-    <string name="print_error_retry" msgid="1426421728784259538">"נסה שוב"</string>
+    <string name="print_error_retry" msgid="1426421728784259538">"כדאי לנסות שוב"</string>
     <string name="print_error_printer_unavailable" msgid="8985614415253203381">"המדפסת הזו אינה זמינה כעת."</string>
     <string name="print_cannot_load_page" msgid="6179560924492912009">"לא ניתן להציג תצוגה מקדימה"</string>
     <string name="print_preparing_preview" msgid="3939930735671364712">"מכין תצוגה מקדימה…"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
index 5b9281cb..d84e57a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
@@ -40,8 +40,12 @@
     }
 
     @Override
-    public void hidden(Context context, int category) {
-        MetricsLogger.hidden(context, category);
+    public void hidden(Context context, int category, int visibleTime) {
+        final LogMaker logMaker = new LogMaker(category)
+                .setType(MetricsProto.MetricsEvent.TYPE_CLOSE)
+                .addTaggedData(MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
+                        visibleTime);
+        MetricsLogger.action(logMaker);
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
index 9d9c17f..d4ef3d7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
@@ -31,7 +31,7 @@
     /**
      * Logs a visibility event when view becomes hidden.
      */
-    void hidden(Context context, int category);
+    void hidden(Context context, int category, int visibleTime);
 
     /**
      * Logs an user action.
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
index a82231a..c34c365 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -83,9 +83,15 @@
         }
     }
 
-    public void hidden(Context context, int category) {
+    /**
+     * Logs an event when target page is hidden.
+     *
+     * @param category the target page id
+     * @param visibleTime the time spending on target page since being visible
+     */
+    public void hidden(Context context, int category, int visibleTime) {
         for (LogWriter writer : mLoggerWriters) {
-            writer.hidden(context, category);
+            writer.hidden(context, category, visibleTime);
         }
     }
 
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 0a1a122..61e47f8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
@@ -40,7 +40,8 @@
 
     private MetricsFeatureProvider mMetricsFeature;
     private int mSourceMetricsCategory = MetricsProto.MetricsEvent.VIEW_UNKNOWN;
-    private long mTimestamp;
+    private long mCreationTimestamp;
+    private long mVisibleTimestamp;
 
     public VisibilityLoggerMixin(int metricsCategory, MetricsFeatureProvider metricsFeature) {
         mMetricsCategory = metricsCategory;
@@ -49,7 +50,7 @@
 
     @Override
     public void onAttach() {
-        mTimestamp = SystemClock.elapsedRealtime();
+        mCreationTimestamp = SystemClock.elapsedRealtime();
     }
 
     @OnLifecycleEvent(Event.ON_RESUME)
@@ -57,8 +58,9 @@
         if (mMetricsFeature == null || mMetricsCategory == METRICS_CATEGORY_UNKNOWN) {
             return;
         }
-        if (mTimestamp != 0L) {
-            final int elapse = (int) (SystemClock.elapsedRealtime() - mTimestamp);
+        mVisibleTimestamp = SystemClock.elapsedRealtime();
+        if (mCreationTimestamp != 0L) {
+            final int elapse = (int) (mVisibleTimestamp - mCreationTimestamp);
             mMetricsFeature.visible(null /* context */, mSourceMetricsCategory,
                     mMetricsCategory, elapse);
         } else {
@@ -69,9 +71,10 @@
 
     @OnLifecycleEvent(Event.ON_PAUSE)
     public void onPause() {
-        mTimestamp = 0;
+        mCreationTimestamp = 0;
         if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) {
-            mMetricsFeature.hidden(null /* context */, mMetricsCategory);
+            final int elapse = (int) (SystemClock.elapsedRealtime() - mVisibleTimestamp);
+            mMetricsFeature.hidden(null /* context */, mMetricsCategory, elapse);
         }
     }
 
@@ -84,7 +87,7 @@
         if (mMetricsFeature == null || mMetricsCategory == METRICS_CATEGORY_UNKNOWN) {
             return;
         }
-        final int elapse = (int) (SystemClock.elapsedRealtime() - mTimestamp);
+        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/media/MediaOutputSliceConstants.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
index e600cb8..248b118 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaOutputSliceConstants.java
@@ -27,6 +27,11 @@
     public static final String KEY_MEDIA_OUTPUT = "media_output";
 
     /**
+     * Key for the Remote Media slice.
+     */
+    public static final String KEY_REMOTE_MEDIA = "remote_media";
+
+    /**
      * Activity Action: Show a settings dialog containing {@link MediaDevice} to transfer media.
      */
     public static final String ACTION_MEDIA_OUTPUT =
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
index 23e29493..ebca962 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -46,7 +46,7 @@
         }
 
         final String[] mergedSubscriberIds = telephonyManager.createForSubscriptionId(subId)
-                .getMergedSubscriberIdsFromGroup();
+                .getMergedImsisFromGroup();
 
         if (ArrayUtils.isEmpty(mergedSubscriberIds)) {
             Log.i(TAG, "mergedSubscriberIds is null.");
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java
index f070a37..7de36e8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java
@@ -88,7 +88,7 @@
         mMixin.onPause();
 
         verify(mMetricsFeature, times(1))
-                .hidden(nullable(Context.class), eq(TestInstrumentable.TEST_METRIC));
+                .hidden(nullable(Context.class), eq(TestInstrumentable.TEST_METRIC), anyInt());
     }
 
     @Test
@@ -98,7 +98,7 @@
         mMixin.onPause();
 
         verify(mMetricsFeature, never())
-                .hidden(nullable(Context.class), anyInt());
+                .hidden(nullable(Context.class), anyInt(), anyInt());
     }
 
     @Test
@@ -109,7 +109,7 @@
         mMixin.onPause();
 
         verify(mMetricsFeature, never())
-                .hidden(nullable(Context.class), anyInt());
+                .hidden(nullable(Context.class), anyInt(), anyInt());
     }
 
     @Test
@@ -121,7 +121,7 @@
         verify(testActivity.mMetricsFeatureProvider, times(1)).visible(any(), anyInt(), anyInt(),
                 anyInt());
         ac.pause().stop().destroy();
-        verify(testActivity.mMetricsFeatureProvider, times(1)).hidden(any(), anyInt());
+        verify(testActivity.mMetricsFeatureProvider, times(1)).hidden(any(), anyInt(), anyInt());
     }
 
     public static class TestActivity extends FragmentActivity {
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 5cae611..d98f50b 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
@@ -87,7 +87,7 @@
     public void getMobileTemplate_groupUuidNull_returnMobileAll() {
         when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
         when(mInfo1.getGroupUuid()).thenReturn(null);
-        when(mTelephonyManager.getMergedSubscriberIdsFromGroup())
+        when(mTelephonyManager.getMergedImsisFromGroup())
                 .thenReturn(new String[] {SUBSCRIBER_ID});
 
         final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
@@ -99,7 +99,7 @@
     public void getMobileTemplate_groupUuidExist_returnMobileMerged() {
         when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
         when(mInfo1.getGroupUuid()).thenReturn(mParcelUuid);
-        when(mTelephonyManager.getMergedSubscriberIdsFromGroup())
+        when(mTelephonyManager.getMergedImsisFromGroup())
                 .thenReturn(new String[] {SUBSCRIBER_ID, SUBSCRIBER_ID_2});
 
         final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
diff --git a/packages/SettingsProvider/res/values/blocked_settings.xml b/packages/SettingsProvider/res/values/blocked_settings.xml
new file mode 100644
index 0000000..b54b74e
--- /dev/null
+++ b/packages/SettingsProvider/res/values/blocked_settings.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.
+  -->
+
+<!-- These are arrays of settings which should not be restored to this device -->
+<resources>
+    <string-array name="restore_blocked_device_specific_settings" />
+    <string-array name="restore_blocked_global_settings" />
+    <string-array name="restore_blocked_secure_settings" />
+    <string-array name="restore_blocked_system_settings" />
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsProvider/res/values/overlayable.xml b/packages/SettingsProvider/res/values/overlayable.xml
new file mode 100644
index 0000000..dc41a77
--- /dev/null
+++ b/packages/SettingsProvider/res/values/overlayable.xml
@@ -0,0 +1,25 @@
+<?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/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index c19a340..fb558ab 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -66,6 +66,7 @@
 import java.io.OutputStream;
 import java.time.DateTimeException;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Objects;
@@ -250,7 +251,13 @@
 
     @Override
     public void onRestore(BackupDataInput data, int appVersionCode,
-            ParcelFileDescriptor newState) throws IOException {
+            ParcelFileDescriptor newState) {
+        throw new RuntimeException("SettingsBackupAgent has been migrated to use key exclusion");
+    }
+
+    @Override
+    public void onRestore(BackupDataInput data, long appVersionCode,
+            ParcelFileDescriptor newState, Set<String> dynamicBlockList) throws IOException {
 
         if (DEBUG) {
             Log.d(TAG, "onRestore(): appVersionCode: " + appVersionCode
@@ -266,7 +273,7 @@
         }
 
         // versionCode of com.android.providers.settings corresponds to SDK_INT
-        mRestoredFromSdkInt = appVersionCode;
+        mRestoredFromSdkInt = (int) appVersionCode;
 
         HashSet<String> movedToGlobal = new HashSet<String>();
         Settings.System.getMovedToGlobalSettings(movedToGlobal);
@@ -292,16 +299,29 @@
             switch (key) {
                 case KEY_SYSTEM :
                     restoreSettings(data, Settings.System.CONTENT_URI, movedToGlobal,
-                            movedToSecure);
+                            movedToSecure, R.array.restore_blocked_system_settings,
+                            dynamicBlockList);
                     mSettingsHelper.applyAudioSettings();
                     break;
 
                 case KEY_SECURE :
-                    restoreSettings(data, Settings.Secure.CONTENT_URI, movedToGlobal, null);
+                    restoreSettings(
+                            data,
+                            Settings.Secure.CONTENT_URI,
+                            movedToGlobal,
+                            null,
+                            R.array.restore_blocked_secure_settings,
+                            dynamicBlockList);
                     break;
 
                 case KEY_GLOBAL :
-                    restoreSettings(data, Settings.Global.CONTENT_URI, null, movedToSecure);
+                    restoreSettings(
+                            data,
+                            Settings.Global.CONTENT_URI,
+                            null,
+                            movedToSecure,
+                            R.array.restore_blocked_global_settings,
+                            dynamicBlockList);
                     break;
 
                 case KEY_WIFI_SUPPLICANT :
@@ -345,7 +365,10 @@
                 case KEY_DEVICE_SPECIFIC_CONFIG:
                     byte[] restoredDeviceSpecificConfig = new byte[size];
                     data.readEntityData(restoredDeviceSpecificConfig, 0, size);
-                    restoreDeviceSpecificConfig(restoredDeviceSpecificConfig);
+                    restoreDeviceSpecificConfig(
+                            restoredDeviceSpecificConfig,
+                            R.array.restore_blocked_device_specific_settings,
+                            dynamicBlockList);
                     break;
 
                 default :
@@ -394,14 +417,22 @@
             byte[] buffer = new byte[nBytes];
             in.readFully(buffer, 0, nBytes);
             restoreSettings(buffer, nBytes, Settings.System.CONTENT_URI, movedToGlobal,
-                    movedToSecure);
+                    movedToSecure, R.array.restore_blocked_system_settings,
+                    Collections.emptySet());
 
             // secure settings
             nBytes = in.readInt();
             if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of secure settings data");
             if (nBytes > buffer.length) buffer = new byte[nBytes];
             in.readFully(buffer, 0, nBytes);
-            restoreSettings(buffer, nBytes, Settings.Secure.CONTENT_URI, movedToGlobal, null);
+            restoreSettings(
+                    buffer,
+                    nBytes,
+                    Settings.Secure.CONTENT_URI,
+                    movedToGlobal,
+                    null,
+                    R.array.restore_blocked_secure_settings,
+                    Collections.emptySet());
 
             // Global only if sufficiently new
             if (version >= FULL_BACKUP_ADDED_GLOBAL) {
@@ -411,7 +442,8 @@
                 in.readFully(buffer, 0, nBytes);
                 movedToGlobal.clear();  // no redirection; this *is* the global namespace
                 restoreSettings(buffer, nBytes, Settings.Global.CONTENT_URI, movedToGlobal,
-                        movedToSecure);
+                        movedToSecure, R.array.restore_blocked_global_settings,
+                        Collections.emptySet());
             }
 
             // locale
@@ -612,8 +644,13 @@
         return baos.toByteArray();
     }
 
-    private void restoreSettings(BackupDataInput data, Uri contentUri,
-            HashSet<String> movedToGlobal, Set<String> movedToSecure) {
+    private void restoreSettings(
+            BackupDataInput data,
+            Uri contentUri,
+            HashSet<String> movedToGlobal,
+            Set<String> movedToSecure,
+            int blockedSettingsArrayId,
+            Set<String> dynamicBlockList) {
         byte[] settings = new byte[data.getDataSize()];
         try {
             data.readEntityData(settings, 0, settings.length);
@@ -621,16 +658,44 @@
             Log.e(TAG, "Couldn't read entity data");
             return;
         }
-        restoreSettings(settings, settings.length, contentUri, movedToGlobal, movedToSecure);
+        restoreSettings(
+                settings,
+                settings.length,
+                contentUri,
+                movedToGlobal,
+                movedToSecure,
+                blockedSettingsArrayId,
+                dynamicBlockList);
     }
 
-    private void restoreSettings(byte[] settings, int bytes, Uri contentUri,
-            HashSet<String> movedToGlobal, Set<String> movedToSecure) {
-        restoreSettings(settings, 0, bytes, contentUri, movedToGlobal, movedToSecure);
+    private void restoreSettings(
+            byte[] settings,
+            int bytes,
+            Uri contentUri,
+            HashSet<String> movedToGlobal,
+            Set<String> movedToSecure,
+            int blockedSettingsArrayId,
+            Set<String> dynamicBlockList) {
+        restoreSettings(
+                settings,
+                0,
+                bytes,
+                contentUri,
+                movedToGlobal,
+                movedToSecure,
+                blockedSettingsArrayId,
+                dynamicBlockList);
     }
 
-    private void restoreSettings(byte[] settings, int pos, int bytes, Uri contentUri,
-                HashSet<String> movedToGlobal, Set<String> movedToSecure) {
+    private void restoreSettings(
+            byte[] settings,
+            int pos,
+            int bytes,
+            Uri contentUri,
+            HashSet<String> movedToGlobal,
+            Set<String> movedToSecure,
+            int blockedSettingsArrayId,
+            Set<String> dynamicBlockList) {
         if (DEBUG) {
             Log.i(TAG, "restoreSettings: " + contentUri);
         }
@@ -662,9 +727,20 @@
         SettingsHelper settingsHelper = mSettingsHelper;
         ContentResolver cr = getContentResolver();
 
-        final int whiteListSize = whitelist.length;
-        for (int i = 0; i < whiteListSize; i++) {
-            String key = whitelist[i];
+        Set<String> blockedSettings = getBlockedSettings(blockedSettingsArrayId);
+
+        for (String key : whitelist) {
+            boolean isBlockedBySystem = blockedSettings != null && blockedSettings.contains(key);
+            if (isBlockedBySystem || isBlockedByDynamicList(dynamicBlockList, contentUri,  key)) {
+                Log.i(
+                        TAG,
+                        "Key "
+                                + key
+                                + " removed from restore by "
+                                + (isBlockedBySystem ? "system" : "dynamic")
+                                + " block list");
+                continue;
+            }
 
             String value = null;
             boolean hasValueToRestore = false;
@@ -722,6 +798,19 @@
         }
     }
 
+    private boolean isBlockedByDynamicList(Set<String> dynamicBlockList, Uri areaUri, String key) {
+        String contentKey = Uri.withAppendedPath(areaUri, key).toString();
+        return dynamicBlockList.contains(contentKey);
+    }
+
+    // There may be other sources of blocked settings, so I'm separating out this
+    // code to make it easy to modify in the future.
+    @VisibleForTesting
+    protected Set<String> getBlockedSettings(int blockedSettingsArrayId) {
+        String[] blockedSettings = getResources().getStringArray(blockedSettingsArrayId);
+        return new HashSet<>(Arrays.asList(blockedSettings));
+    }
+
     private boolean isValidSettingValue(String key, String value,
             Map<String, Validator> validators) {
         if (key == null || validators == null) {
@@ -998,10 +1087,13 @@
      * Restore the device specific settings.
      *
      * @param data The byte array holding a backed up version of another devices settings.
+     * @param blockedSettingsArrayId The string array resource holding the settings not to restore.
+     * @param dynamicBlocklist The dynamic list of settings not to restore fed into this agent.
      * @return true if the restore succeeded, false if it was stopped.
      */
     @VisibleForTesting
-    boolean restoreDeviceSpecificConfig(byte[] data) {
+    boolean restoreDeviceSpecificConfig(byte[] data, int blockedSettingsArrayId,
+            Set<String> dynamicBlocklist) {
         // We're using an AtomicInteger to wrap the position int and allow called methods to
         // modify it.
         AtomicInteger pos = new AtomicInteger(0);
@@ -1013,7 +1105,14 @@
 
         int dataStart = pos.get();
         restoreSettings(
-                data, dataStart, data.length, Settings.Secure.CONTENT_URI, null, null);
+                data,
+                dataStart,
+                data.length,
+                Settings.Secure.CONTENT_URI,
+                null,
+                null,
+                blockedSettingsArrayId,
+                dynamicBlocklist);
 
         updateWindowManagerIfNeeded(originalDensity);
 
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
index 57e22db..e650882 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
@@ -37,14 +37,14 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -85,12 +85,33 @@
 
         assertEquals("Not all values backed up.", TEST_VALUES.keySet(), helper.mReadEntries);
 
-        mAgentUnderTest.restoreDeviceSpecificConfig(settingsBackup);
+        mAgentUnderTest.restoreDeviceSpecificConfig(
+                settingsBackup,
+                R.array.restore_blocked_device_specific_settings,
+                Collections.emptySet());
 
         assertEquals("Not all values were restored.", TEST_VALUES, helper.mWrittenValues);
     }
 
     @Test
+    public void testRoundTripDeviceSpecificSettingsWithBlock() throws IOException {
+        TestSettingsHelper helper = new TestSettingsHelper(mContext);
+        mAgentUnderTest.mSettingsHelper = helper;
+
+        byte[] settingsBackup = mAgentUnderTest.getDeviceSpecificConfiguration();
+
+        assertEquals("Not all values backed up.", TEST_VALUES.keySet(), helper.mReadEntries);
+        mAgentUnderTest.setBlockedSettings(TEST_VALUES.keySet().toArray(new String[0]));
+
+        mAgentUnderTest.restoreDeviceSpecificConfig(
+                settingsBackup,
+                R.array.restore_blocked_device_specific_settings,
+                Collections.emptySet());
+
+        assertTrue("Not all values were blocked.", helper.mWrittenValues.isEmpty());
+    }
+
+    @Test
     public void testGeneratedHeaderMatchesCurrentDevice() throws IOException {
         mAgentUnderTest.mSettingsHelper = new TestSettingsHelper(mContext);
 
@@ -148,7 +169,10 @@
 
         assertFalse(
                 "Blocking isSourceAcceptable did not stop restore",
-                mAgentUnderTest.restoreDeviceSpecificConfig(data));
+                mAgentUnderTest.restoreDeviceSpecificConfig(
+                        data,
+                        R.array.restore_blocked_device_specific_settings,
+                        Collections.emptySet()));
     }
 
     private byte[] generateUncorruptedHeader() throws IOException {
@@ -184,14 +208,34 @@
         }
     }
 
+    private byte[] generateSingleKeyTestBackupData(String key, String value) throws IOException {
+        try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+            os.write(SettingsBackupAgent.toByteArray(key));
+            os.write(SettingsBackupAgent.toByteArray(value));
+            return os.toByteArray();
+        }
+    }
+
     private static class TestFriendlySettingsBackupAgent extends SettingsBackupAgent {
         private Boolean mForcedDeviceInfoRestoreAcceptability = null;
+        private String[] mBlockedSettings = null;
 
         void setForcedDeviceInfoRestoreAcceptability(boolean value) {
             mForcedDeviceInfoRestoreAcceptability = value;
         }
 
-        @VisibleForTesting
+        void setBlockedSettings(String... blockedSettings) {
+            mBlockedSettings = blockedSettings;
+        }
+
+        @Override
+        protected Set<String> getBlockedSettings(int blockedSettingsArrayId) {
+            return mBlockedSettings == null
+                    ? super.getBlockedSettings(blockedSettingsArrayId)
+                    : new HashSet<>(Arrays.asList(mBlockedSettings));
+        }
+
+        @Override
         boolean isSourceAcceptable(byte[] data, AtomicInteger pos) {
             return mForcedDeviceInfoRestoreAcceptability == null
                     ? super.isSourceAcceptable(data, pos)
diff --git a/packages/Shell/res/values-uk/strings.xml b/packages/Shell/res/values-uk/strings.xml
index 28f3aec..f62d117 100644
--- a/packages/Shell/res/values-uk/strings.xml
+++ b/packages/Shell/res/values-uk/strings.xml
@@ -18,27 +18,27 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="3701846017049540910">"Оболонка"</string>
     <string name="bugreport_notification_channel" msgid="2574150205913861141">"Звіти про помилки"</string>
-    <string name="bugreport_in_progress_title" msgid="4311705936714972757">"Генерується повідомлення про помилку <xliff:g id="ID">#%d</xliff:g>"</string>
-    <string name="bugreport_finished_title" msgid="4429132808670114081">"Повідомлення про помилку <xliff:g id="ID">#%d</xliff:g> створено"</string>
-    <string name="bugreport_updating_title" msgid="4423539949559634214">"Додаються деталі до повідомлення про помилку"</string>
+    <string name="bugreport_in_progress_title" msgid="4311705936714972757">"Створюється звіт про помилку <xliff:g id="ID">#%d</xliff:g>"</string>
+    <string name="bugreport_finished_title" msgid="4429132808670114081">"Звіт про помилку <xliff:g id="ID">#%d</xliff:g> створено"</string>
+    <string name="bugreport_updating_title" msgid="4423539949559634214">"У звіт про помилку додаються деталі"</string>
     <string name="bugreport_updating_wait" msgid="3322151947853929470">"Зачекайте…"</string>
     <string name="bugreport_finished_text" product="watch" msgid="1223616207145252689">"Звіт про помилку невдовзі з’явиться на телефоні"</string>
-    <string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"Виберіть, щоб надіслати повідомлення про помилку"</string>
-    <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"Торкніться, щоб надіслати повідомлення про помилку"</string>
+    <string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"Виберіть, щоб надіслати звіт про помилку"</string>
+    <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"Торкніться, щоб надіслати звіт про помилку"</string>
     <string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"Виберіть, щоб надіслати повідомлення про помилку без знімка екрана або зачекайте на знімок"</string>
-    <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"Торкніться, щоб надіслати повідомлення про помилку без знімка екрана або зачекайте на знімок"</string>
-    <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"Торкніться, щоб надіслати повідомлення про помилку без знімка екрана або зачекайте на знімок"</string>
+    <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"Торкніться, щоб надіслати звіт про помилку без знімка екрана, або зачекайте, доки буде збережено знімок"</string>
+    <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"Торкніться, щоб надіслати звіт про помилку без знімка екрана, або зачекайте, доки буде збережено знімок"</string>
     <string name="bugreport_confirm" msgid="5917407234515812495">"Звіти про помилки містять дані з різних файлів журналів системи, зокрема відомості, які ви вважаєте конфіденційними (як-от інформація про місцезнаходження та використання додатка). Діліться звітами про помилки лише з людьми та в додатках, яким довіряєте."</string>
     <string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"Більше не показувати"</string>
     <string name="bugreport_storage_title" msgid="5332488144740527109">"Звіти про помилки"</string>
-    <string name="bugreport_unreadable_text" msgid="586517851044535486">"Не вдалося прочитати звіт про помилки"</string>
-    <string name="bugreport_add_details_to_zip_failed" msgid="1302931926486712371">"Не вдалося додати деталі повідомлення про помилку у файл .zip"</string>
+    <string name="bugreport_unreadable_text" msgid="586517851044535486">"Не вдалося прочитати звіт про помилку"</string>
+    <string name="bugreport_add_details_to_zip_failed" msgid="1302931926486712371">"Не вдалося додати деталі звіту про помилку у файл .zip"</string>
     <string name="bugreport_unnamed" msgid="2800582406842092709">"без назви"</string>
     <string name="bugreport_info_action" msgid="2158204228510576227">"Деталі"</string>
     <string name="bugreport_screenshot_action" msgid="8677781721940614995">"Знімок екрана"</string>
     <string name="bugreport_screenshot_taken" msgid="5684211273096253120">"Знімок екрана зроблено."</string>
     <string name="bugreport_screenshot_failed" msgid="5853049140806834601">"Не вдалося зробити знімок екрана."</string>
-    <string name="bugreport_info_dialog_title" msgid="1355948594292983332">"Деталі повідомлення про помилку <xliff:g id="ID">#%d</xliff:g>"</string>
+    <string name="bugreport_info_dialog_title" msgid="1355948594292983332">"Деталі звіту про помилку <xliff:g id="ID">#%d</xliff:g>"</string>
     <string name="bugreport_info_name" msgid="4414036021935139527">"Назва файлу"</string>
     <string name="bugreport_info_title" msgid="2306030793918239804">"Назва помилки"</string>
     <string name="bugreport_info_description" msgid="5072835127481627722">"Опис помилки"</string>
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index a9ca04b..eecc54c 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -267,7 +267,6 @@
     @Inject Lazy<KeyguardEnvironment> mKeyguardEnvironment;
     @Inject Lazy<ShadeController> mShadeController;
     @Inject Lazy<NotificationRemoteInputManager.Callback> mNotificationRemoteInputManagerCallback;
-    @Inject Lazy<InitController> mInitController;
     @Inject Lazy<AppOpsController> mAppOpsController;
     @Inject Lazy<NavigationBarController> mNavigationBarController;
     @Inject Lazy<StatusBarStateController> mStatusBarStateController;
@@ -456,8 +455,6 @@
         mProviders.put(NotificationRemoteInputManager.Callback.class,
                 mNotificationRemoteInputManagerCallback::get);
 
-        mProviders.put(InitController.class, mInitController::get);
-
         mProviders.put(AppOpsController.class, mAppOpsController::get);
 
         mProviders.put(NavigationBarController.class, mNavigationBarController::get);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index e08de39..1315152 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -56,6 +56,7 @@
     private SystemUI[] mServices;
     private boolean mServicesStarted;
     private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
+    private SystemUIRootComponent mRootComponent;
 
     public SystemUIApplication() {
         super();
@@ -72,9 +73,9 @@
                 Trace.TRACE_TAG_APP);
         log.traceBegin("DependencyInjection");
         mContextAvailableCallback.onContextAvailable(this);
-        SystemUIRootComponent root = SystemUIFactory.getInstance().getRootComponent();
-        mComponentHelper = root.getContextComponentHelper();
-        mBootCompleteCache = root.provideBootCacheImpl();
+        mRootComponent = SystemUIFactory.getInstance().getRootComponent();
+        mComponentHelper = mRootComponent.getContextComponentHelper();
+        mBootCompleteCache = mRootComponent.provideBootCacheImpl();
         log.traceEnd();
 
         // Set the application theme that is inherited by all services. Note that setting the
@@ -209,7 +210,7 @@
                 mServices[i].onBootCompleted();
             }
         }
-        Dependency.get(InitController.class).executePostInitTasks();
+        mRootComponent.getInitController().executePostInitTasks();
         log.traceEnd();
 
         mServicesStarted = true;
@@ -218,11 +219,7 @@
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         if (mServicesStarted) {
-            SystemUIFactory
-                    .getInstance()
-                    .getRootComponent()
-                    .getConfigurationController()
-                    .onConfigurationChanged(newConfig);
+            mRootComponent.getConfigurationController().onConfigurationChanged(newConfig);
             int len = mServices.length;
             for (int i = 0; i < len; i++) {
                 if (mServices[i] != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java b/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java
index a726b42..d5a46de 100644
--- a/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java
+++ b/packages/SystemUI/src/com/android/systemui/UiOffloadThread.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT 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;
@@ -36,7 +36,7 @@
     public UiOffloadThread() {
     }
 
-    public Future<?> submit(Runnable runnable) {
+    public Future<?> execute(Runnable runnable) {
         return mExecutorService.submit(runnable);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
index 20742d6..eb014ed 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
@@ -37,8 +37,8 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.Dependency;
-import com.android.systemui.UiOffloadThread;
 import com.android.systemui.analytics.DataCollector;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -46,6 +46,7 @@
 import com.android.systemui.util.sensors.AsyncSensorManager;
 
 import java.io.PrintWriter;
+import java.util.concurrent.Executor;
 
 /**
  * When the phone is locked, listens to touch, sensor and phone events and sends them to
@@ -77,7 +78,7 @@
     private final DataCollector mDataCollector;
     private final HumanInteractionClassifier mHumanInteractionClassifier;
     private final AccessibilityManager mAccessibilityManager;
-    private final UiOffloadThread mUiOffloadThread;
+    private final Executor mUiBgExecutor;
 
     private boolean mEnforceBouncer = false;
     private boolean mBouncerOn = false;
@@ -137,13 +138,13 @@
                 }
             };
 
-    FalsingManagerImpl(Context context) {
+    FalsingManagerImpl(Context context, @UiBackground Executor uiBgExecutor) {
         mContext = context;
         mSensorManager = Dependency.get(AsyncSensorManager.class);
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mDataCollector = DataCollector.getInstance(mContext);
         mHumanInteractionClassifier = HumanInteractionClassifier.getInstance(mContext);
-        mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+        mUiBgExecutor = uiBgExecutor;
         mScreenOn = context.getSystemService(PowerManager.class).isInteractive();
         mMetricsLogger = new MetricsLogger();
 
@@ -196,7 +197,7 @@
             }
 
             // This can be expensive, and doesn't need to happen on the main thread.
-            mUiOffloadThread.submit(() -> {
+            mUiBgExecutor.execute(() -> {
                 mSensorManager.unregisterListener(mSensorEventListener);
             });
         }
@@ -237,7 +238,7 @@
             if (s != null) {
 
                 // This can be expensive, and doesn't need to happen on the main thread.
-                mUiOffloadThread.submit(() -> {
+                mUiBgExecutor.execute(() -> {
                     mSensorManager.registerListener(
                             mSensorEventListener, s, SensorManager.SENSOR_DELAY_GAME);
                 });
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index 4663b1c..f475948 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -31,6 +31,7 @@
 import com.android.systemui.classifier.brightline.BrightLineFalsingManager;
 import com.android.systemui.classifier.brightline.FalsingDataProvider;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.FalsingPlugin;
 import com.android.systemui.plugins.PluginListener;
@@ -39,6 +40,7 @@
 import com.android.systemui.util.sensors.ProximitySensor;
 
 import java.io.PrintWriter;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -58,13 +60,16 @@
     private DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener;
     private final DeviceConfigProxy mDeviceConfig;
     private boolean mBrightlineEnabled;
+    private Executor mUiBgExecutor;
 
     @Inject
     FalsingManagerProxy(Context context, PluginManager pluginManager,
             @Main Handler handler,
             ProximitySensor proximitySensor,
-            DeviceConfigProxy deviceConfig) {
+            DeviceConfigProxy deviceConfig,
+            @UiBackground Executor uiBgExecutor) {
         mProximitySensor = proximitySensor;
+        mUiBgExecutor = uiBgExecutor;
         mProximitySensor.setTag(PROXIMITY_SENSOR_TAG);
         mProximitySensor.setSensorDelay(SensorManager.SENSOR_DELAY_GAME);
         mDeviceConfig = deviceConfig;
@@ -87,7 +92,7 @@
             }
 
             public void onPluginDisconnected(FalsingPlugin plugin) {
-                mInternalFalsingManager = new FalsingManagerImpl(context);
+                mInternalFalsingManager = new FalsingManagerImpl(context, mUiBgExecutor);
             }
         };
 
@@ -117,7 +122,7 @@
             mInternalFalsingManager.cleanup();
         }
         if (!brightlineEnabled) {
-            mInternalFalsingManager = new FalsingManagerImpl(context);
+            mInternalFalsingManager = new FalsingManagerImpl(context, mUiBgExecutor);
         } else {
             mInternalFalsingManager = new BrightLineFalsingManager(
                     new FalsingDataProvider(context.getResources().getDisplayMetrics()),
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index 5fc789c..5c171e4 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -30,7 +30,7 @@
 import com.android.systemui.power.EnhancedEstimatesImpl;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImplementation;
-import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.stackdivider.DividerModule;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
@@ -44,13 +44,10 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
-import java.util.Optional;
-
 import javax.inject.Named;
 import javax.inject.Singleton;
 
 import dagger.Binds;
-import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
@@ -58,7 +55,7 @@
  * A dagger module for injecting default implementations of components of System UI that may be
  * overridden by the System UI implementation.
  */
-@Module
+@Module(includes = {DividerModule.class})
 abstract class SystemUIDefaultModule {
 
     @Singleton
@@ -95,12 +92,6 @@
 
     @Singleton
     @Provides
-    static Divider provideDivider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy) {
-        return new Divider(context, recentsOptionalLazy);
-    }
-
-    @Singleton
-    @Provides
     static HeadsUpManagerPhone provideHeadsUpManagerPhone(Context context,
             StatusBarStateController statusBarStateController,
             KeyguardBypassController bypassController) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
index e50e0fe0..e14581f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java
@@ -22,6 +22,7 @@
 
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.Dependency;
+import com.android.systemui.InitController;
 import com.android.systemui.SystemUIAppComponentFactory;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.fragments.FragmentService;
@@ -77,6 +78,13 @@
     @Singleton
     FragmentService.FragmentCreator createFragmentCreator();
 
+
+    /**
+     * Creates a InitController.
+     */
+    @Singleton
+    InitController getInitController();
+
     /**
      * ViewCreator generates all Views that need injection.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/UiBackground.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/UiBackground.java
new file mode 100644
index 0000000..bf2237a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/UiBackground.java
@@ -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.
+ */
+
+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;
+
+
+/**
+ * An annotation for injecting instances related to UI operations off the main-thread.
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface UiBackground {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
index 25ac8f8..f6f3b99 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/DismissCallbackRegistry.java
@@ -11,16 +11,16 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT 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.keyguard;
 
 import com.android.internal.policy.IKeyguardDismissCallback;
-import com.android.systemui.Dependency;
-import com.android.systemui.UiOffloadThread;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -32,10 +32,12 @@
 public class DismissCallbackRegistry {
 
     private final ArrayList<DismissCallbackWrapper> mDismissCallbacks = new ArrayList<>();
-    private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+    private final Executor mUiBgExecutor;
 
     @Inject
-    public DismissCallbackRegistry() {}
+    public DismissCallbackRegistry(@UiBackground Executor uiBgExecutor) {
+        mUiBgExecutor = uiBgExecutor;
+    }
 
     public void addCallback(IKeyguardDismissCallback callback) {
         mDismissCallbacks.add(new DismissCallbackWrapper(callback));
@@ -44,7 +46,7 @@
     public void notifyDismissCancelled() {
         for (int i = mDismissCallbacks.size() - 1; i >= 0; i--) {
             DismissCallbackWrapper callback = mDismissCallbacks.get(i);
-            mUiOffloadThread.submit(callback::notifyDismissCancelled);
+            mUiBgExecutor.execute(callback::notifyDismissCancelled);
         }
         mDismissCallbacks.clear();
     }
@@ -52,7 +54,7 @@
     public void notifyDismissSucceeded() {
         for (int i = mDismissCallbacks.size() - 1; i >= 0; i--) {
             DismissCallbackWrapper callback = mDismissCallbacks.get(i);
-            mUiOffloadThread.submit(callback::notifyDismissSucceeded);
+            mUiBgExecutor.execute(callback::notifyDismissSucceeded);
         }
         mDismissCallbacks.clear();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index beba203..9fcf022 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT 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.keyguard;
@@ -81,8 +81,8 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.SystemUIFactory;
-import com.android.systemui.UiOffloadThread;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -95,6 +95,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -211,7 +212,7 @@
     private AudioManager mAudioManager;
     private StatusBarManager mStatusBarManager;
     private final StatusBarWindowController mStatusBarWindowController;
-    private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+    private final Executor mUiBgExecutor;
 
     private boolean mSystemReady;
     private boolean mBootCompleted;
@@ -689,7 +690,8 @@
             BroadcastDispatcher broadcastDispatcher,
             StatusBarWindowController statusBarWindowController,
             Lazy<StatusBarKeyguardViewManager> statusBarKeyguardViewManagerLazy,
-            DismissCallbackRegistry dismissCallbackRegistry) {
+            DismissCallbackRegistry dismissCallbackRegistry,
+            @UiBackground Executor uiBgExecutor) {
         super(context);
         mFalsingManager = falsingManager;
         mLockPatternUtils = lockPatternUtils;
@@ -697,6 +699,7 @@
         mStatusBarWindowController = statusBarWindowController;
         mStatusBarKeyguardViewManagerLazy = statusBarKeyguardViewManagerLazy;
         mDismissCallbackRegistry = dismissCallbackRegistry;
+        mUiBgExecutor = uiBgExecutor;
     }
 
     public void userActivity() {
@@ -1662,7 +1665,7 @@
     private void handleKeyguardDone() {
         Trace.beginSection("KeyguardViewMediator#handleKeyguardDone");
         final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
-        mUiOffloadThread.submit(() -> {
+        mUiBgExecutor.execute(() -> {
             if (mLockPatternUtils.isSecure(currentUser)) {
                 mLockPatternUtils.getDevicePolicyManager().reportKeyguardDismissed(currentUser);
             }
@@ -1705,7 +1708,7 @@
                 final UserHandle currentUser = new UserHandle(currentUserId);
                 final UserManager um = (UserManager) mContext.getSystemService(
                         Context.USER_SERVICE);
-                mUiOffloadThread.submit(() -> {
+                mUiBgExecutor.execute(() -> {
                     for (int profileId : um.getProfileIdsWithDisabled(currentUser.getIdentifier())) {
                         mContext.sendBroadcastAsUser(USER_PRESENT_INTENT, UserHandle.of(profileId));
                     }
@@ -1756,7 +1759,7 @@
                 mUiSoundsStreamType = mAudioManager.getUiSoundsStreamType();
             }
 
-            mUiOffloadThread.submit(() -> {
+            mUiBgExecutor.execute(() -> {
                 // If the stream is muted, don't play the sound
                 if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
 
@@ -1775,7 +1778,7 @@
     }
 
     private void updateActivityLockScreenState(boolean showing, boolean aodShowing) {
-        mUiOffloadThread.submit(() -> {
+        mUiBgExecutor.execute(() -> {
             if (DEBUG) {
                 Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
             }
@@ -1854,7 +1857,7 @@
             // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager will be in
             // order.
             final int keyguardFlag = flags;
-            mUiOffloadThread.submit(() -> {
+            mUiBgExecutor.execute(() -> {
                 try {
                     ActivityTaskManager.getService().keyguardGoingAway(keyguardFlag);
                 } catch (RemoteException e) {
@@ -2217,7 +2220,7 @@
             }
         });
         updateInputRestrictedLocked();
-        mUiOffloadThread.submit(() -> {
+        mUiBgExecutor.execute(() -> {
             mTrustManager.reportKeyguardShowingChanged();
         });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 0a89017..f39d1ec 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -100,7 +100,7 @@
             mMenuController.onActivityPinned();
             mAppOpsListener.onActivityPinned(packageName);
 
-            Dependency.get(UiOffloadThread.class).submit(() -> {
+            Dependency.get(UiOffloadThread.class).execute(() -> {
                 WindowManagerWrapper.getInstance().setPipVisibility(true);
             });
         }
@@ -114,7 +114,7 @@
             mTouchHandler.onActivityUnpinned(topActivity);
             mAppOpsListener.onActivityUnpinned();
 
-            Dependency.get(UiOffloadThread.class).submit(() -> {
+            Dependency.get(UiOffloadThread.class).execute(() -> {
                 WindowManagerWrapper.getInstance().setPipVisibility(topActivity != null);
             });
         }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 696db68..1d92375 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -750,7 +750,7 @@
     }
 
     private void updatePipVisibility(final boolean visible) {
-        Dependency.get(UiOffloadThread.class).submit(() -> {
+        Dependency.get(UiOffloadThread.class).execute(() -> {
             WindowManagerWrapper.getInstance().setPipVisibility(visible);
         });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java
new file mode 100644
index 0000000..49f4d5e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.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.systemui.stackdivider;
+
+import android.content.Context;
+
+import com.android.systemui.recents.Recents;
+
+import java.util.Optional;
+
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Module which provides a Divider.
+ */
+@Module
+public class DividerModule {
+    @Singleton
+    @Provides
+    static Divider provideDivider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy) {
+        return new Divider(context, recentsOptionalLazy);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index e24a362..b846aa0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -28,7 +28,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 
 import java.util.stream.Stream;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index 49bed15..93f5805 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -54,12 +54,13 @@
 import com.android.systemui.DockedStackExistsListener;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
-import com.android.systemui.UiOffloadThread;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.NotificationChannels;
 
 import java.util.List;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -74,16 +75,18 @@
     public static final int NUM_TASKS_FOR_INSTANT_APP_INFO = 5;
 
     private final Handler mHandler = new Handler();
-    private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+    private final Executor mUiBgExecutor;
     private final ArraySet<Pair<String, Integer>> mCurrentNotifs = new ArraySet<>();
     private final CommandQueue mCommandQueue;
     private boolean mDockedStackExists;
     private KeyguardStateController mKeyguardStateController;
 
     @Inject
-    public InstantAppNotifier(Context context, CommandQueue commandQueue) {
+    public InstantAppNotifier(Context context, CommandQueue commandQueue,
+            @UiBackground Executor uiBgExecutor) {
         super(context);
         mCommandQueue = commandQueue;
+        mUiBgExecutor = uiBgExecutor;
     }
 
     @Override
@@ -151,7 +154,7 @@
     private void updateForegroundInstantApps() {
         NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
         IPackageManager pm = AppGlobals.getPackageManager();
-        mUiOffloadThread.submit(
+        mUiBgExecutor.execute(
                 () -> {
                     ArraySet<Pair<String, Integer>> notifs = new ArraySet<>(mCurrentNotifs);
                     try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
index 31b7cb0..81833a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.notification;
 
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY;
-import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_HEADS_UP;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 
 import android.app.Notification;
 import android.service.notification.StatusBarNotification;
@@ -28,7 +28,7 @@
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import javax.inject.Inject;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
index 0694920..f6b5583 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
@@ -24,7 +24,7 @@
 
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 
 /**
  * Listener interface for changes sent by NotificationEntryManager.
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 43b9fbc..b8afb78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -18,6 +18,8 @@
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_ERROR;
 
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Notification;
@@ -45,8 +47,7 @@
 import com.android.systemui.statusbar.notification.logging.NotifEvent;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -94,7 +95,7 @@
 @Singleton
 public class NotificationEntryManager implements
         Dumpable,
-        NotificationContentInflater.InflationCallback,
+        InflationCallback,
         VisualStabilityManager.Callback {
     private static final String TAG = "NotificationEntryMgr";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
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 de16ef5..dd3a3e0 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
@@ -61,8 +61,8 @@
 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.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
 import com.android.systemui.statusbar.notification.row.NotificationGuts;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
 
 import java.util.ArrayList;
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
index 1c0a9d4..8afbc27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
@@ -18,7 +18,7 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
-import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_HEADS_UP;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -258,7 +258,7 @@
         row.setEntry(entry);
 
         if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
-            row.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, true /* shouldInflate */);
+            row.setInflationFlags(FLAG_CONTENT_VIEW_HEADS_UP);
         }
         row.setNeedsRedaction(
                 Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 77ccf19..3e1b5bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT 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.notification.logging;
 
@@ -32,7 +32,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.UiOffloadThread;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.NotificationListener;
@@ -47,6 +47,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -68,7 +69,7 @@
 
     // Dependencies:
     private final NotificationListenerService mNotificationListener;
-    private final UiOffloadThread mUiOffloadThread;
+    private final Executor mUiBgExecutor;
     private final NotificationEntryManager mEntryManager;
     private HeadsUpManager mHeadsUpManager;
     private final ExpansionStateLogger mExpansionStateLogger;
@@ -193,12 +194,12 @@
 
     @Inject
     public NotificationLogger(NotificationListener notificationListener,
-            UiOffloadThread uiOffloadThread,
+            @UiBackground Executor uiBgExecutor,
             NotificationEntryManager entryManager,
             StatusBarStateController statusBarStateController,
             ExpansionStateLogger expansionStateLogger) {
         mNotificationListener = notificationListener;
-        mUiOffloadThread = uiOffloadThread;
+        mUiBgExecutor = uiBgExecutor;
         mEntryManager = entryManager;
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -319,7 +320,7 @@
         final NotificationVisibility[] newlyVisibleAr = cloneVisibilitiesAsArr(newlyVisible);
         final NotificationVisibility[] noLongerVisibleAr = cloneVisibilitiesAsArr(noLongerVisible);
 
-        mUiOffloadThread.submit(() -> {
+        mUiBgExecutor.execute(() -> {
             try {
                 mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr);
             } catch (RemoteException e) {
@@ -429,13 +430,13 @@
          * Notification key -> last logged expansion state, should be accessed in UI thread only.
          */
         private final Map<String, Boolean> mLoggedExpansionState = new ArrayMap<>();
-        private final UiOffloadThread mUiOffloadThread;
+        private final Executor mUiBgExecutor;
         @VisibleForTesting
         IStatusBarService mBarService;
 
         @Inject
-        public ExpansionStateLogger(UiOffloadThread uiOffloadThread) {
-            mUiOffloadThread = uiOffloadThread;
+        public ExpansionStateLogger(@UiBackground Executor uiBgExecutor) {
+            mUiBgExecutor = uiBgExecutor;
             mBarService =
                     IStatusBarService.Stub.asInterface(
                             ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -513,7 +514,7 @@
             }
             mLoggedExpansionState.put(key, state.mIsExpanded);
             final State stateToBeLogged = new State(state);
-            mUiOffloadThread.submit(() -> {
+            mUiBgExecutor.execute(() -> {
                 try {
                     mBarService.onNotificationExpansionChanged(key, stateToBeLogged.mIsUserAction,
                             stateToBeLogged.mIsExpanded, stateToBeLogged.mLocation.ordinal());
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 65423e9..3c247df 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
@@ -17,11 +17,12 @@
 package com.android.systemui.statusbar.notification.row;
 
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
-import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_HEADS_UP;
-import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_PUBLIC;
-import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationCallback;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
+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 com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -88,7 +89,9 @@
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -124,8 +127,18 @@
     private static final String TAG = "ExpandableNotifRow";
     public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
     private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
+
+    /**
+     * Content views that must be inflated at all times.
+     */
+    @InflationFlag
+    static final int REQUIRED_INFLATION_FLAGS =
+            FLAG_CONTENT_VIEW_CONTRACTED
+            | FLAG_CONTENT_VIEW_EXPANDED;
+
     private boolean mUpdateBackgroundOnUpdate;
     private boolean mNotificationTranslationFinished = false;
+
     /**
      * Listener for when {@link ExpandableNotificationRow} is laid out.
      */
@@ -232,6 +245,10 @@
     private ExpandableNotificationRow mNotificationParent;
     private OnExpandClickListener mOnExpandClickListener;
     private View.OnClickListener mOnAppOpsClickListener;
+    private InflationCallback mInflationCallback;
+    private boolean mIsChildInGroup;
+    private @InflationFlag int mInflationFlags = REQUIRED_INFLATION_FLAGS;
+    private final BindParams mBindParams = new BindParams();
 
     // Listener will be called when receiving a long click event.
     // Use #setLongPressPosition to optionally assign positional data with the long press.
@@ -447,7 +464,8 @@
      * Inflate views based off the inflation flags set. Inflation happens asynchronously.
      */
     public void inflateViews() {
-        mNotificationInflater.inflateNotificationViews();
+        mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams,
+                false /* forceInflate */, mInflationCallback);
     }
 
     /**
@@ -458,9 +476,9 @@
      */
     public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {
         // View should not be reinflated in the future
-        updateInflationFlag(inflationFlag, false);
-        Runnable freeViewRunnable = () ->
-                mNotificationInflater.freeNotificationView(inflationFlag);
+        clearInflationFlags(inflationFlag);
+        Runnable freeViewRunnable =
+                () -> mNotificationInflater.unbindContent(mEntry, this, inflationFlag);
         switch (inflationFlag) {
             case FLAG_CONTENT_VIEW_HEADS_UP:
                 getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP,
@@ -475,13 +493,22 @@
     }
 
     /**
-     * Update whether or not a content view should be inflated.
+     * Set flags for content views that should be inflated
      *
-     * @param flag the flag corresponding to the content view
-     * @param shouldInflate true if it should be inflated, false if it should not
+     * @param flags flags to inflate
      */
-    public void updateInflationFlag(@InflationFlag int flag, boolean shouldInflate) {
-        mNotificationInflater.updateInflationFlag(flag, shouldInflate);
+    public void setInflationFlags(@InflationFlag int flags) {
+        mInflationFlags |= flags;
+    }
+
+    /**
+     * Clear flags for content views that should not be inflated
+     *
+     * @param flags flags that should not be inflated
+     */
+    public void clearInflationFlags(@InflationFlag int flags) {
+        mInflationFlags &= ~flags;
+        mInflationFlags |= REQUIRED_INFLATION_FLAGS;
     }
 
     /**
@@ -491,7 +518,7 @@
      * @return true if the flag is set, false otherwise
      */
     public boolean isInflationFlagSet(@InflationFlag int flag) {
-        return mNotificationInflater.isInflationFlagSet(flag);
+        return ((mInflationFlags & flag) != 0);
     }
 
     /**
@@ -820,7 +847,15 @@
         }
         mNotificationParent = isChildInGroup ? parent : null;
         mPrivateLayout.setIsChildInGroup(isChildInGroup);
-        mNotificationInflater.setIsChildInGroup(isChildInGroup);
+        mBindParams.isChildInGroup = isChildInGroup;
+        if (mIsChildInGroup != isChildInGroup) {
+            mIsChildInGroup = isChildInGroup;
+            if (mIsLowPriority) {
+                int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
+                mNotificationInflater.bindContent(mEntry, this, flags, mBindParams,
+                        false /* forceInflate */, mInflationCallback);
+            }
+        }
         resetBackgroundAlpha();
         updateBackgroundForGroupState();
         updateClickAndFocus();
@@ -1224,7 +1259,8 @@
             l.reInflateViews();
         }
         mEntry.getSbn().clearPackageContext();
-        mNotificationInflater.clearCachesAndReInflate();
+        mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams,
+                true /* forceInflate */, mInflationCallback);
     }
 
     @Override
@@ -1578,7 +1614,7 @@
     public void setIsLowPriority(boolean isLowPriority) {
         mIsLowPriority = isLowPriority;
         mPrivateLayout.setIsLowPriority(isLowPriority);
-        mNotificationInflater.setIsLowPriority(mIsLowPriority);
+        mBindParams.isLowPriority = mIsLowPriority;
         if (mChildrenContainer != null) {
             mChildrenContainer.setIsLowPriority(isLowPriority);
         }
@@ -1590,28 +1626,36 @@
 
     public void setUseIncreasedCollapsedHeight(boolean use) {
         mUseIncreasedCollapsedHeight = use;
-        mNotificationInflater.setUsesIncreasedHeight(use);
+        mBindParams.usesIncreasedHeight = use;
     }
 
     public void setUseIncreasedHeadsUpHeight(boolean use) {
         mUseIncreasedHeadsUpHeight = use;
-        mNotificationInflater.setUsesIncreasedHeadsUpHeight(use);
+        mBindParams.usesIncreasedHeadsUpHeight = use;
     }
 
     public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
         mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler);
     }
 
+    /**
+     * Set callback for notification content inflation
+     *
+     * @param callback inflation callback
+     */
     public void setInflationCallback(InflationCallback callback) {
-        mNotificationInflater.setInflationCallback(callback);
+        mInflationCallback = callback;
     }
 
     public void setNeedsRedaction(boolean needsRedaction) {
         if (mNeedsRedaction != needsRedaction) {
             mNeedsRedaction = needsRedaction;
-            updateInflationFlag(FLAG_CONTENT_VIEW_PUBLIC, needsRedaction /* shouldInflate */);
-            mNotificationInflater.updateNeedsRedaction(needsRedaction);
-            if (!needsRedaction) {
+            if (needsRedaction) {
+                setInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
+                mNotificationInflater.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC,
+                        mBindParams, false /* forceInflate */, mInflationCallback);
+            } else {
+                clearInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
                 freeContentViewWhenSafe(FLAG_CONTENT_VIEW_PUBLIC);
             }
         }
@@ -1628,7 +1672,7 @@
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mNotificationInflater = new NotificationContentInflater(this);
+        mNotificationInflater = new NotificationContentInflater();
         mMenuRow = new NotificationMenuRow(mContext);
         mImageResolver = new NotificationInlineImageResolver(context,
                 new NotificationInlineImageCache());
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 54dee8c..172b72e 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
@@ -19,7 +19,7 @@
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
 
-import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Notification;
 import android.content.Context;
@@ -47,205 +47,55 @@
 import com.android.systemui.statusbar.policy.SmartReplyConstants;
 import com.android.systemui.util.Assert;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.HashMap;
 
 /**
- * A utility that inflates the right kind of contentView based on the state
+ * {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by
+ * asynchronously building the content's {@link RemoteViews} and applying it to the row.
  */
-public class NotificationContentInflater {
+public class NotificationContentInflater implements NotificationRowContentBinder {
 
     public static final String TAG = "NotifContentInflater";
 
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = true,
-            prefix = {"FLAG_CONTENT_VIEW_"},
-            value = {
-                FLAG_CONTENT_VIEW_CONTRACTED,
-                FLAG_CONTENT_VIEW_EXPANDED,
-                FLAG_CONTENT_VIEW_HEADS_UP,
-                FLAG_CONTENT_VIEW_PUBLIC,
-                FLAG_CONTENT_VIEW_ALL})
-    public @interface InflationFlag {}
-    /**
-     * The default, contracted view.  Seen when the shade is pulled down and in the lock screen
-     * if there is no worry about content sensitivity.
-     */
-    public static final int FLAG_CONTENT_VIEW_CONTRACTED = 1;
-
-    /**
-     * The expanded view.  Seen when the user expands a notification.
-     */
-    public static final int FLAG_CONTENT_VIEW_EXPANDED = 1 << 1;
-
-    /**
-     * The heads up view.  Seen when a high priority notification peeks in from the top.
-     */
-    public static final int FLAG_CONTENT_VIEW_HEADS_UP = 1 << 2;
-
-    /**
-     * The public view.  This is a version of the contracted view that hides sensitive
-     * information and is used on the lock screen if we determine that the notification's
-     * content should be hidden.
-     */
-    public static final int FLAG_CONTENT_VIEW_PUBLIC = 1 << 3;
-
-    public static final int FLAG_CONTENT_VIEW_ALL = ~0;
-
-    /**
-     * Content views that must be inflated at all times.
-     */
-    @InflationFlag
-    private static final int REQUIRED_INFLATION_FLAGS =
-            FLAG_CONTENT_VIEW_CONTRACTED
-            | FLAG_CONTENT_VIEW_EXPANDED;
-
-    /**
-     * The set of content views to inflate.
-     */
-    @InflationFlag
-    private int mInflationFlags = REQUIRED_INFLATION_FLAGS;
-
-    private final ExpandableNotificationRow mRow;
-    private boolean mIsLowPriority;
-    private boolean mUsesIncreasedHeight;
-    private boolean mUsesIncreasedHeadsUpHeight;
     private RemoteViews.OnClickHandler mRemoteViewClickHandler;
-    private boolean mIsChildInGroup;
-    private InflationCallback mCallback;
     private boolean mInflateSynchronously = false;
     private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>();
 
-    public NotificationContentInflater(ExpandableNotificationRow row) {
-        mRow = row;
-    }
-
-    public void setIsLowPriority(boolean isLowPriority) {
-        mIsLowPriority = isLowPriority;
-    }
-
-    /**
-     * Set whether the notification is a child in a group
-     *
-     * @return whether the view was re-inflated
-     */
-    public void setIsChildInGroup(boolean childInGroup) {
-        if (childInGroup != mIsChildInGroup) {
-            mIsChildInGroup = childInGroup;
-            if (mIsLowPriority) {
-                int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
-                inflateNotificationViews(flags);
-            }
-        }
-    }
-
-    public void setUsesIncreasedHeight(boolean usesIncreasedHeight) {
-        mUsesIncreasedHeight = usesIncreasedHeight;
-    }
-
-    public void setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight) {
-        mUsesIncreasedHeadsUpHeight = usesIncreasedHeight;
-    }
-
-    public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
-        mRemoteViewClickHandler = remoteViewClickHandler;
-    }
-
-    /**
-     * Update whether or not the notification is redacted on the lock screen.  If the notification
-     * is now redacted, we should inflate the public contracted view to now show on the lock screen.
-     *
-     * @param needsRedaction true if the notification should now be redacted on the lock screen
-     */
-    public void updateNeedsRedaction(boolean needsRedaction) {
-        if (mRow.getEntry() == null) {
-            return;
-        }
-        if (needsRedaction) {
-            int flags = FLAG_CONTENT_VIEW_PUBLIC;
-            inflateNotificationViews(flags);
-        }
-    }
-
-    /**
-     * Set whether or not a particular content view is needed and whether or not it should be
-     * inflated.  These flags will be used when we inflate or reinflate.
-     *
-     * @param flag the {@link InflationFlag} corresponding to the view that should/should not be
-     *             inflated
-     * @param shouldInflate true if the view should be inflated, false otherwise
-     */
-    public void updateInflationFlag(@InflationFlag int flag, boolean shouldInflate) {
-        if (shouldInflate) {
-            mInflationFlags |= flag;
-        } else if ((REQUIRED_INFLATION_FLAGS & flag) == 0) {
-            mInflationFlags &= ~flag;
-        }
-    }
-
-    /**
-     * Convenience method for setting multiple flags at once.
-     *
-     * @param flags a set of {@link InflationFlag} corresponding to content views that should be
-     *              inflated
-     */
-    @VisibleForTesting
-    public void addInflationFlags(@InflationFlag int flags) {
-        mInflationFlags |= flags;
-    }
-
-    /**
-     * Whether or not the view corresponding to the flag is set to be inflated currently.
-     *
-     * @param flag the {@link InflationFlag} corresponding to the view
-     * @return true if the flag is set and view will be inflated, false o/w
-     */
-    public boolean isInflationFlagSet(@InflationFlag int flag) {
-        return ((mInflationFlags & flag) != 0);
-    }
-
-    /**
-     * Inflate views for set flags on a background thread. This is asynchronous and will
-     * notify the callback once it's finished.
-     */
-    public void inflateNotificationViews() {
-        inflateNotificationViews(mInflationFlags);
-    }
-
-    /**
-     * Inflate all views for the specified flags on a background thread.  This is asynchronous and
-     * will notify the callback once it's finished.  If the content view is already inflated, this
-     * will reinflate it.
-     *
-     * @param reInflateFlags flags which views should be inflated. Should be a subset of
-     *                       {@link #mInflationFlags} as only those will be inflated/reinflated.
-     */
-    private void inflateNotificationViews(@InflationFlag int reInflateFlags) {
-        if (mRow.isRemoved()) {
+    @Override
+    public void bindContent(
+            NotificationEntry entry,
+            ExpandableNotificationRow row,
+            @InflationFlag int contentToBind,
+            BindParams bindParams,
+            boolean forceInflate,
+            @Nullable InflationCallback callback) {
+        if (row.isRemoved()) {
             // We don't want to reinflate anything for removed notifications. Otherwise views might
             // be readded to the stack, leading to leaks. This may happen with low-priority groups
             // where the removal of already removed children can lead to a reinflation.
             return;
         }
-        // Only inflate the ones that are set.
-        reInflateFlags &= mInflationFlags;
-        StatusBarNotification sbn = mRow.getEntry().getSbn();
+
+        StatusBarNotification sbn = row.getEntry().getSbn();
 
         // To check if the notification has inline image and preload inline image if necessary.
-        mRow.getImageResolver().preloadImages(sbn.getNotification());
+        row.getImageResolver().preloadImages(sbn.getNotification());
+
+        if (forceInflate) {
+            mCachedContentViews.clear();
+        }
 
         AsyncInflationTask task = new AsyncInflationTask(
                 sbn,
                 mInflateSynchronously,
-                reInflateFlags,
+                contentToBind,
                 mCachedContentViews,
-                mRow,
-                mIsLowPriority,
-                mIsChildInGroup,
-                mUsesIncreasedHeight,
-                mUsesIncreasedHeadsUpHeight,
-                mCallback,
+                row,
+                bindParams.isLowPriority,
+                bindParams.isChildInGroup,
+                bindParams.usesIncreasedHeight,
+                bindParams.usesIncreasedHeadsUpHeight,
+                callback,
                 mRemoteViewClickHandler);
         if (mInflateSynchronously) {
             task.onPostExecute(task.doInBackground());
@@ -256,49 +106,84 @@
 
     @VisibleForTesting
     InflationProgress inflateNotificationViews(
+            NotificationEntry entry,
+            ExpandableNotificationRow row,
+            BindParams bindParams,
             boolean inflateSynchronously,
             @InflationFlag int reInflateFlags,
             Notification.Builder builder,
             Context packageContext) {
-        InflationProgress result = createRemoteViews(reInflateFlags, builder, mIsLowPriority,
-                mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight,
+        InflationProgress result = createRemoteViews(reInflateFlags,
+                builder,
+                bindParams.isLowPriority,
+                bindParams.isChildInGroup,
+                bindParams.usesIncreasedHeight,
+                bindParams.usesIncreasedHeadsUpHeight,
                 packageContext);
-        result = inflateSmartReplyViews(result, reInflateFlags, mRow.getEntry(),
-                mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
-                mRow.getExistingSmartRepliesAndActions());
+        result = inflateSmartReplyViews(result, reInflateFlags, entry,
+                row.getContext(), packageContext, row.getHeadsUpManager(),
+                row.getExistingSmartRepliesAndActions());
         apply(
                 inflateSynchronously,
                 result,
                 reInflateFlags,
                 mCachedContentViews,
-                mRow,
+                row,
                 mRemoteViewClickHandler,
                 null);
         return result;
     }
 
+    @Override
+    public void cancelBind(
+            @NonNull NotificationEntry entry,
+            @NonNull ExpandableNotificationRow row) {
+        entry.abortTask();
+    }
+
+    @Override
+    public void unbindContent(
+            @NonNull NotificationEntry entry,
+            @NonNull ExpandableNotificationRow row,
+            @InflationFlag int contentToUnbind) {
+        int curFlag = 1;
+        while (contentToUnbind != 0) {
+            if ((contentToUnbind & curFlag) != 0) {
+                freeNotificationView(row, curFlag);
+            }
+            contentToUnbind &= ~curFlag;
+            curFlag = curFlag << 1;
+        }
+    }
+
+    /**
+     * 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
      */
-    public void freeNotificationView(@InflationFlag int inflateFlag) {
-        if ((mInflationFlags & inflateFlag) != 0) {
-            // The view should still be inflated.
-            return;
-        }
+    private void freeNotificationView(ExpandableNotificationRow row,
+            @InflationFlag int inflateFlag) {
         switch (inflateFlag) {
             case FLAG_CONTENT_VIEW_HEADS_UP:
-                if (mRow.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) {
-                    mRow.getPrivateLayout().setHeadsUpChild(null);
+                if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) {
+                    row.getPrivateLayout().setHeadsUpChild(null);
                     mCachedContentViews.remove(FLAG_CONTENT_VIEW_HEADS_UP);
-                    mRow.getPrivateLayout().setHeadsUpInflatedSmartReplies(null);
+                    row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null);
                 }
                 break;
             case FLAG_CONTENT_VIEW_PUBLIC:
-                if (mRow.getPublicLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) {
-                    mRow.getPublicLayout().setContractedChild(null);
+                if (row.getPublicLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) {
+                    row.getPublicLayout().setContractedChild(null);
                     mCachedContentViews.remove(FLAG_CONTENT_VIEW_PUBLIC);
                 }
                 break;
@@ -718,33 +603,12 @@
                         && !oldView.hasFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED));
     }
 
-    public void setInflationCallback(InflationCallback callback) {
-        mCallback = callback;
-    }
-
-    public interface InflationCallback {
-        void handleInflationException(StatusBarNotification notification, Exception e);
-
-        /**
-         * Callback for after the content views finish inflating.
-         *
-         * @param entry the entry with the content views set
-         * @param inflatedFlags the flags associated with the content views that were inflated
-         */
-        void onAsyncInflationFinished(NotificationEntry entry, @InflationFlag int inflatedFlags);
-    }
-
-    public void clearCachesAndReInflate() {
-        mCachedContentViews.clear();
-        inflateNotificationViews();
-    }
-
     /**
      * Sets whether to perform inflation on the same thread as the caller. This method should only
      * be used in tests, not in production.
      */
     @VisibleForTesting
-    void setInflateSynchronously(boolean inflateSynchronously) {
+    public void setInflateSynchronously(boolean inflateSynchronously) {
         mInflateSynchronously = inflateSynchronously;
     }
 
@@ -842,8 +706,10 @@
             final String ident = sbn.getPackageName() + "/0x"
                     + Integer.toHexString(sbn.getId());
             Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
-            mCallback.handleInflationException(sbn,
-                    new InflationException("Couldn't inflate contentViews" + e));
+            if (mCallback != null) {
+                mCallback.handleInflationException(sbn,
+                        new InflationException("Couldn't inflate contentViews" + e));
+            }
         }
 
         @Override
@@ -872,7 +738,9 @@
                 @InflationFlag int inflatedFlags) {
             mRow.getEntry().onInflationTaskFinished();
             mRow.onNotificationUpdated();
-            mCallback.onAsyncInflationFinished(mRow.getEntry(), inflatedFlags);
+            if (mCallback != null) {
+                mCallback.onAsyncInflationFinished(mRow.getEntry(), inflatedFlags);
+            }
 
             // Notify the resolver that the inflation task has finished,
             // try to purge unnecessary cached entries.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
new file mode 100644
index 0000000..2fe54c0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -0,0 +1,155 @@
+/*
+ * 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.row;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.service.notification.StatusBarNotification;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Binder that takes a notifications {@link ExpandableNotificationRow} and binds the appropriate
+ * content to it based off the bind parameters passed to it.
+ */
+public interface NotificationRowContentBinder {
+
+    /**
+     * Inflate notification content views and bind to the row.
+     *
+     * @param entry notification
+     * @param row notification row to bind views to
+     * @param contentToBind content views that should be inflated and bound
+     * @param bindParams parameters for binding content views
+     * @param forceInflate true to force reinflation even if views are cached
+     * @param callback callback after inflation is finished
+     */
+    void bindContent(
+            @NonNull NotificationEntry entry,
+            @NonNull ExpandableNotificationRow row,
+            @InflationFlag int contentToBind,
+            BindParams bindParams,
+            boolean forceInflate,
+            @Nullable InflationCallback callback);
+
+    /**
+     * Cancel any on-going bind operation.
+     *
+     * @param entry notification
+     * @param row notification row to cancel bind on
+     */
+    void cancelBind(
+            @NonNull NotificationEntry entry,
+            @NonNull ExpandableNotificationRow row);
+
+    /**
+     * Unbind content views from the row.
+     *
+     * @param entry notification
+     * @param row notification row to unbind content views from
+     * @param contentToUnbind content views that should be unbound
+     */
+    void unbindContent(
+            @NonNull NotificationEntry entry,
+            @NonNull ExpandableNotificationRow row,
+            @InflationFlag int contentToUnbind);
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true,
+            prefix = {"FLAG_CONTENT_VIEW_"},
+            value = {
+                    FLAG_CONTENT_VIEW_CONTRACTED,
+                    FLAG_CONTENT_VIEW_EXPANDED,
+                    FLAG_CONTENT_VIEW_HEADS_UP,
+                    FLAG_CONTENT_VIEW_PUBLIC,
+                    FLAG_CONTENT_VIEW_ALL})
+    @interface InflationFlag {}
+    /**
+     * The default, contracted view.  Seen when the shade is pulled down and in the lock screen
+     * if there is no worry about content sensitivity.
+     */
+    int FLAG_CONTENT_VIEW_CONTRACTED = 1;
+    /**
+     * The expanded view.  Seen when the user expands a notification.
+     */
+    int FLAG_CONTENT_VIEW_EXPANDED = 1 << 1;
+    /**
+     * The heads up view.  Seen when a high priority notification peeks in from the top.
+     */
+    int FLAG_CONTENT_VIEW_HEADS_UP = 1 << 2;
+    /**
+     * The public view.  This is a version of the contracted view that hides sensitive
+     * information and is used on the lock screen if we determine that the notification's
+     * content should be hidden.
+     */
+    int FLAG_CONTENT_VIEW_PUBLIC = 1 << 3;
+
+    int FLAG_CONTENT_VIEW_ALL = ~0;
+
+    /**
+     * Parameters for content view binding
+     */
+    class BindParams {
+
+        /**
+         * Bind a low priority version of the content views.
+         */
+        public boolean isLowPriority;
+
+        /**
+         * Bind child version of content views.
+         */
+        public boolean isChildInGroup;
+
+        /**
+         * Use increased height when binding contracted view.
+         */
+        public boolean usesIncreasedHeight;
+
+        /**
+         * Use increased height when binding heads up views.
+         */
+        public boolean usesIncreasedHeadsUpHeight;
+    }
+
+    /**
+     * Callback for inflation finishing
+     */
+    interface InflationCallback {
+
+        /**
+         * Callback for when there is an inflation exception
+         *
+         * @param notification notification which failed to inflate content
+         * @param e exception
+         */
+        void handleInflationException(StatusBarNotification notification, Exception e);
+
+        /**
+         * Callback for after the content views finish inflating.
+         *
+         * @param entry the entry with the content views set
+         * @param inflatedFlags the flags associated with the content views that were inflated
+         */
+        void onAsyncInflationFinished(NotificationEntry entry,
+                @InflationFlag int inflatedFlags);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
index 90ea6e3..0a1a2fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT 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.notification.row.wrapper;
@@ -244,17 +244,18 @@
                 mUiOffloadThread = Dependency.get(UiOffloadThread.class);
             }
             if (view.isAttachedToWindow()) {
-                mUiOffloadThread.submit(() -> pendingIntent.registerCancelListener(listener));
+                mUiOffloadThread.execute(() -> pendingIntent.registerCancelListener(listener));
             }
             view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
                 @Override
                 public void onViewAttachedToWindow(View v) {
-                    mUiOffloadThread.submit(() -> pendingIntent.registerCancelListener(listener));
+                    mUiOffloadThread.execute(() -> pendingIntent.registerCancelListener(listener));
                 }
 
                 @Override
                 public void onViewDetachedFromWindow(View v) {
-                    mUiOffloadThread.submit(() -> pendingIntent.unregisterCancelListener(listener));
+                    mUiOffloadThread.execute(
+                            () -> pendingIntent.unregisterCancelListener(listener));
                 }
             });
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
index 1df9411..d6336ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
@@ -53,7 +53,7 @@
 import android.util.SparseBooleanArray;
 
 import com.android.systemui.Dumpable;
-import com.android.systemui.UiOffloadThread;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
@@ -61,6 +61,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -82,7 +83,7 @@
     private Context mCurrentUserContext;
     private final IOverlayManager mOverlayManager;
     private final DeviceProvisionedController mDeviceProvisionedController;
-    private final UiOffloadThread mUiOffloadThread;
+    private final Executor mUiBgExecutor;
 
     private SparseBooleanArray mRestoreGesturalNavBarMode = new SparseBooleanArray();
 
@@ -146,12 +147,12 @@
     @Inject
     public NavigationModeController(Context context,
             DeviceProvisionedController deviceProvisionedController,
-            UiOffloadThread uiOffloadThread) {
+            @UiBackground Executor uiBgExecutor) {
         mContext = context;
         mCurrentUserContext = context;
         mOverlayManager = IOverlayManager.Stub.asInterface(
                 ServiceManager.getService(Context.OVERLAY_SERVICE));
-        mUiOffloadThread = uiOffloadThread;
+        mUiBgExecutor = uiBgExecutor;
         mDeviceProvisionedController = deviceProvisionedController;
         mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback);
 
@@ -242,7 +243,7 @@
         mCurrentUserContext = getCurrentUserContext();
         int mode = getCurrentInteractionMode(mCurrentUserContext);
         mMode = mode;
-        mUiOffloadThread.submit(() -> {
+        mUiBgExecutor.execute(() -> {
             Settings.Secure.putString(mCurrentUserContext.getContentResolver(),
                     Secure.NAVIGATION_MODE, String.valueOf(mode));
         });
@@ -379,7 +380,7 @@
     }
 
     public void setModeOverlay(String overlayPkg, int userId) {
-        mUiOffloadThread.submit(() -> {
+        mUiBgExecutor.execute(() -> {
             try {
                 mOverlayManager.setEnabledExclusiveInCategory(overlayPkg, userId);
                 if (DEBUG) {
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 2798285..fe0739f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -33,7 +33,7 @@
 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.NotificationContentInflater.InflationFlag;
+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;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -395,7 +395,7 @@
         @InflationFlag int contentFlag = alertManager.getContentFlag();
         if (!entry.getRow().isInflationFlagSet(contentFlag)) {
             mPendingAlerts.put(entry.getKey(), new PendingAlertInfo(entry));
-            entry.getRow().updateInflationFlag(contentFlag, true /* shouldInflate */);
+            entry.getRow().setInflationFlags(contentFlag);
             entry.getRow().inflateViews();
             return;
         }
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 2f39c13..5b34aa7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -40,8 +40,8 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.UiOffloadThread;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.RotationLockTile;
 import com.android.systemui.statusbar.CommandQueue;
@@ -63,6 +63,7 @@
 import com.android.systemui.statusbar.policy.ZenModeController;
 
 import java.util.Locale;
+import java.util.concurrent.Executor;
 
 /**
  * This class contains all of the policy about which icons are installed in the status bar at boot
@@ -114,7 +115,7 @@
     private final DeviceProvisionedController mProvisionedController;
     private final KeyguardStateController mKeyguardStateController;
     private final LocationController mLocationController;
-    private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
+    private final Executor mUiBgExecutor;
     private final SensorPrivacyController mSensorPrivacyController;
 
     // Assume it's all good unless we hear otherwise.  We don't always seem
@@ -131,7 +132,8 @@
     private AlarmManager.AlarmClockInfo mNextAlarm;
 
     public PhoneStatusBarPolicy(Context context, StatusBarIconController iconController,
-            CommandQueue commandQueue, BroadcastDispatcher broadcastDispatcher) {
+            CommandQueue commandQueue, BroadcastDispatcher broadcastDispatcher,
+            @UiBackground Executor uiBgExecutor) {
         mContext = context;
         mIconController = iconController;
         mCast = Dependency.get(CastController.class);
@@ -148,6 +150,7 @@
         mKeyguardStateController = Dependency.get(KeyguardStateController.class);
         mLocationController = Dependency.get(LocationController.class);
         mSensorPrivacyController = Dependency.get(SensorPrivacyController.class);
+        mUiBgExecutor = uiBgExecutor;
 
         mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast);
         mSlotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot);
@@ -452,7 +455,7 @@
         // getLastResumedActivityUserId needds to acquire the AM lock, which may be contended in
         // some cases. Since it doesn't really matter here whether it's updated in this frame
         // or in the next one, we call this method from our UI offload thread.
-        mUiOffloadThread.submit(() -> {
+        mUiBgExecutor.execute(() -> {
             final int userId;
             try {
                 userId = ActivityTaskManager.getService().getLastResumedActivityUserId();
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 312f9c1..e96e258 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -30,6 +30,7 @@
 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;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
@@ -136,13 +137,13 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.SystemUIFactory;
-import com.android.systemui.UiOffloadThread;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.charging.WirelessChargingAnimation;
 import com.android.systemui.classifier.FalsingLog;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.fragments.ExtensionFragmentListener;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
@@ -232,6 +233,7 @@
 import java.io.StringWriter;
 import java.util.Map;
 import java.util.Optional;
+import java.util.concurrent.Executor;
 
 import javax.inject.Named;
 import javax.inject.Provider;
@@ -383,6 +385,8 @@
     private final ShadeController mShadeController;
     private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
     private final LightsOutNotifController mLightsOutNotifController;
+    private final InitController mInitController;
+    private final DarkIconDispatcher mDarkIconDispatcher;
     private final DismissCallbackRegistry mDismissCallbackRegistry;
 
     // expanded notifications
@@ -471,7 +475,7 @@
     private ViewMediatorCallback mKeyguardViewMediatorCallback;
     private final ScrimController mScrimController;
     protected DozeScrimController mDozeScrimController;
-    private final UiOffloadThread mUiOffloadThread;
+    private final Executor mUiBgExecutor;
 
     protected boolean mDozing;
 
@@ -634,7 +638,7 @@
             NotificationAlertingManager notificationAlertingManager,
             DisplayMetrics displayMetrics,
             MetricsLogger metricsLogger,
-            UiOffloadThread uiOffloadThread,
+            @UiBackground Executor uiBgExecutor,
             NotificationMediaManager notificationMediaManager,
             NotificationLockscreenUserManager lockScreenUserManager,
             NotificationRemoteInputManager remoteInputManager,
@@ -680,6 +684,9 @@
             SuperStatusBarViewFactory superStatusBarViewFactory,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
+            InitController initController,
+            DarkIconDispatcher darkIconDispatcher,
+            @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
             DismissCallbackRegistry dismissCallbackRegistry) {
         super(context);
         mFeatureFlags = featureFlags;
@@ -708,7 +715,7 @@
         mNotificationAlertingManager = notificationAlertingManager;
         mDisplayMetrics = displayMetrics;
         mMetricsLogger = metricsLogger;
-        mUiOffloadThread = uiOffloadThread;
+        mUiBgExecutor = uiBgExecutor;
         mMediaManager = notificationMediaManager;
         mLockscreenUserManager = lockScreenUserManager;
         mRemoteInputManager = remoteInputManager;
@@ -753,6 +760,8 @@
         mLightsOutNotifController =  lightsOutNotifController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mKeyguardViewMediatorCallback = viewMediatorCallback;
+        mInitController = initController;
+        mDarkIconDispatcher = darkIconDispatcher;
         mDismissCallbackRegistry = dismissCallbackRegistry;
 
         mBubbleExpandListener =
@@ -760,6 +769,9 @@
                     mEntryManager.updateNotifications("onBubbleExpandChanged");
                     updateScrimController();
                 };
+
+
+        DateTimeView.setReceiverHandler(timeTickHandler);
     }
 
     @Override
@@ -794,8 +806,6 @@
         mVibrateOnOpening = mContext.getResources().getBoolean(
                 R.bool.config_vibrateOnIconAnimation);
 
-        DateTimeView.setReceiverHandler(Dependency.get(Dependency.TIME_TICK_HANDLER));
-
         // start old BaseStatusBar.start().
         mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
         mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
@@ -885,7 +895,7 @@
 
         // Lastly, call to the icon policy to install/update all the icons.
         mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController, mCommandQueue,
-                mBroadcastDispatcher);
+                mBroadcastDispatcher, mUiBgExecutor);
         mSignalPolicy = new StatusBarSignalPolicy(mContext, mIconController);
 
         mKeyguardStateController.addCallback(this);
@@ -902,7 +912,7 @@
         // set the initial view visibility
         int disabledFlags1 = result.mDisabledFlags1;
         int disabledFlags2 = result.mDisabledFlags2;
-        Dependency.get(InitController.class).addPostInitTask(
+        mInitController.addPostInitTask(
                 () -> setUpDisableFlags(disabledFlags1, disabledFlags2));
 
         mPluginManager.addPluginListener(
@@ -983,7 +993,7 @@
         mNotificationPanel.setOnReinflationListener(mNotificationIconAreaController::initAodIcons);
         mNotificationPanel.addExpansionListener(mWakeUpCoordinator);
 
-        Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mNotificationIconAreaController);
+        mDarkIconDispatcher.addDarkReceiver(mNotificationIconAreaController);
         // Allow plugins to reference DarkIconDispatcher and StatusBarStateController
         Dependency.get(PluginDependencyProvider.class)
                 .allowPluginDependency(DarkIconDispatcher.class);
@@ -1231,7 +1241,7 @@
                 mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController,
                 mNotificationAlertingManager, rowBinder, mKeyguardStateController,
                 mKeyguardIndicationController,
-                this /* statusBar */, mShadeController, mCommandQueue);
+                this /* statusBar */, mShadeController, mCommandQueue, mInitController);
 
         mNotificationListController =
                 new NotificationListController(
@@ -1331,7 +1341,7 @@
         // TODO: Bring these out of StatusBar.
         ((UserInfoControllerImpl) Dependency.get(UserInfoController.class))
                 .onDensityOrFontScaleChanged();
-        Dependency.get(UserSwitcherController.class).onDensityOrFontScaleChanged();
+        mUserSwitcherController.onDensityOrFontScaleChanged();
         if (mKeyguardUserSwitcher != null) {
             mKeyguardUserSwitcher.onDensityOrFontScaleChanged();
         }
@@ -2840,7 +2850,7 @@
                 notificationLoad = 1;
             }
             final int finalNotificationLoad = notificationLoad;
-            mUiOffloadThread.submit(() -> {
+            mUiBgExecutor.execute(() -> {
                 try {
                     mBarService.onPanelRevealed(clearNotificationEffects,
                             finalNotificationLoad);
@@ -2849,7 +2859,7 @@
                 }
             });
         } else {
-            mUiOffloadThread.submit(() -> {
+            mUiBgExecutor.execute(() -> {
                 try {
                     mBarService.onPanelHidden();
                 } catch (RemoteException ex) {
@@ -3423,8 +3433,7 @@
         // ringing.
         // Other transitions are covered in handleVisibleToUserChanged().
         if (mVisible && (newState == StatusBarState.SHADE_LOCKED
-                || (((SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class))
-                .goingToFullShade()))) {
+                || mStatusBarStateController.goingToFullShade())) {
             clearNotificationEffects();
         }
         if (newState == StatusBarState.KEYGUARD) {
@@ -4019,7 +4028,7 @@
     }
 
     void awakenDreams() {
-        Dependency.get(UiOffloadThread.class).submit(() -> {
+        mUiBgExecutor.execute(() -> {
             try {
                 mDreamManager.awaken();
             } catch (RemoteException e) {
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 e31c53a..84a763e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
@@ -17,8 +17,10 @@
 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;
+import android.os.Handler;
 import android.os.PowerManager;
 import android.util.DisplayMetrics;
 
@@ -27,15 +29,17 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.UiOffloadThread;
+import com.android.systemui.InitController;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.ScreenPinningRequest;
@@ -75,6 +79,7 @@
 import com.android.systemui.volume.VolumeComponent;
 
 import java.util.Optional;
+import java.util.concurrent.Executor;
 
 import javax.inject.Named;
 import javax.inject.Provider;
@@ -122,7 +127,7 @@
             NotificationAlertingManager notificationAlertingManager,
             DisplayMetrics displayMetrics,
             MetricsLogger metricsLogger,
-            UiOffloadThread uiOffloadThread,
+            @UiBackground Executor uiBgExecutor,
             NotificationMediaManager notificationMediaManager,
             NotificationLockscreenUserManager lockScreenUserManager,
             NotificationRemoteInputManager remoteInputManager,
@@ -168,6 +173,9 @@
             SuperStatusBarViewFactory superStatusBarViewFactory,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             ViewMediatorCallback viewMediatorCallback,
+            InitController initController,
+            DarkIconDispatcher darkIconDispatcher,
+            @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
             DismissCallbackRegistry dismissCallbackRegistry) {
         return new StatusBar(
                 context,
@@ -197,7 +205,7 @@
                 notificationAlertingManager,
                 displayMetrics,
                 metricsLogger,
-                uiOffloadThread,
+                uiBgExecutor,
                 notificationMediaManager,
                 lockScreenUserManager,
                 remoteInputManager,
@@ -242,6 +250,9 @@
                 superStatusBarViewFactory,
                 statusBarKeyguardViewManager,
                 viewMediatorCallback,
+                initController,
+                darkIconDispatcher,
+                timeTickHandler,
                 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 fa584d2..661a7b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -47,13 +47,12 @@
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.ActivityIntentHelper;
-import com.android.systemui.Dependency;
 import com.android.systemui.EventLogTags;
-import com.android.systemui.UiOffloadThread;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
@@ -74,6 +73,8 @@
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import java.util.concurrent.Executor;
+
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
@@ -115,6 +116,7 @@
     private final Handler mBackgroundHandler;
     private final ActivityIntentHelper mActivityIntentHelper;
     private final BubbleController mBubbleController;
+    private final Executor mUiBgExecutor;
 
     private boolean mIsCollapsingToShowActivityOverLockscreen;
 
@@ -133,7 +135,7 @@
             KeyguardStateController keyguardStateController,
             NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils,
-            Handler mainThreadHandler, Handler backgroundHandler,
+            Handler mainThreadHandler, Handler backgroundHandler, Executor uiBgExecutor,
             ActivityIntentHelper activityIntentHelper, BubbleController bubbleController) {
         mContext = context;
         mNotificationPanel = panel;
@@ -160,6 +162,7 @@
         mGroupManager = groupManager;
         mLockPatternUtils = lockPatternUtils;
         mBackgroundHandler = backgroundHandler;
+        mUiBgExecutor = uiBgExecutor;
         mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
             @Override
             public void onPendingEntryAdded(NotificationEntry entry) {
@@ -418,7 +421,7 @@
             } else {
                 // Stop screensaver if the notification has a fullscreen intent.
                 // (like an incoming phone call)
-                Dependency.get(UiOffloadThread.class).submit(() -> {
+                mUiBgExecutor.execute(() -> {
                     try {
                         mDreamManager.awaken();
                     } catch (RemoteException e) {
@@ -521,6 +524,7 @@
         private final LockPatternUtils mLockPatternUtils;
         private final Handler mMainThreadHandler;
         private final Handler mBackgroundHandler;
+        private final Executor mUiBgExecutor;
         private final ActivityIntentHelper mActivityIntentHelper;
         private final BubbleController mBubbleController;
         private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
@@ -551,6 +555,7 @@
                 LockPatternUtils lockPatternUtils,
                 @Main Handler mainThreadHandler,
                 @Background Handler backgroundHandler,
+                @UiBackground Executor uiBgExecutor,
                 ActivityIntentHelper activityIntentHelper,
                 BubbleController bubbleController,
                 ShadeController shadeController,
@@ -576,6 +581,7 @@
             mLockPatternUtils = lockPatternUtils;
             mMainThreadHandler = mainThreadHandler;
             mBackgroundHandler = backgroundHandler;
+            mUiBgExecutor = uiBgExecutor;
             mActivityIntentHelper = activityIntentHelper;
             mBubbleController = bubbleController;
             mShadeController = shadeController;
@@ -624,6 +630,7 @@
                     mLockPatternUtils,
                     mMainThreadHandler,
                     mBackgroundHandler,
+                    mUiBgExecutor,
                     mActivityIntentHelper,
                     mBubbleController);
         }
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 8fc624d..beb4579 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -146,7 +146,8 @@
             KeyguardIndicationController keyguardIndicationController,
             StatusBar statusBar,
             ShadeController shadeController,
-            CommandQueue commandQueue) {
+            CommandQueue commandQueue,
+            InitController initController) {
         mContext = context;
         mKeyguardStateController = keyguardStateController;
         mNotificationPanel = panel;
@@ -193,7 +194,7 @@
                 Dependency.get(StatusBarWindowController.class));
 
         NotificationListContainer notifListContainer = (NotificationListContainer) stackScroller;
-        Dependency.get(InitController.class).addPostInitTask(() -> {
+        initController.addPostInitTask(() -> {
             NotificationEntryListener notificationEntryListener = new NotificationEntryListener() {
                 @Override
                 public void onEntryRemoved(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 6828c32..66a1d3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.policy;
 
-import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_HEADS_UP;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -33,7 +33,7 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
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 1c5d979..7cdba86 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
@@ -24,8 +24,10 @@
 
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.UiBackground;
 
 import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 
 import javax.inject.Singleton;
 
@@ -128,4 +130,16 @@
     public static DelayableExecutor provideMainDelayableExecutor(@Main Looper looper) {
         return new ExecutorImpl(new Handler(looper));
     }
+
+    /**
+     * Provide an Executor specifically for running UI operations on a separate thread.
+     *
+     * Keep submitted runnables short and to the point, just as with any other UI code.
+     */
+    @Provides
+    @Singleton
+    @UiBackground
+    public static Executor provideUiBackgroundExecutor() {
+        return Executors.newSingleThreadExecutor();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/time/SystemClock.java b/packages/SystemUI/src/com/android/systemui/util/time/SystemClock.java
index 4316df1..6fef59f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/time/SystemClock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/time/SystemClock.java
@@ -37,10 +37,4 @@
 
     /** @see android.os.SystemClock#currentThreadTimeMillis() */
     long currentThreadTimeMillis();
-
-    /** @see android.os.SystemClock#currentThreadTimeMicro() */
-    long currentThreadTimeMicro();
-
-    /** @see android.os.SystemClock#currentTimeMicro() */
-    long currentTimeMicro();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/time/SystemClockImpl.java b/packages/SystemUI/src/com/android/systemui/util/time/SystemClockImpl.java
index 532ea05..f0c7014 100644
--- a/packages/SystemUI/src/com/android/systemui/util/time/SystemClockImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/time/SystemClockImpl.java
@@ -42,14 +42,4 @@
     public long currentThreadTimeMillis() {
         return android.os.SystemClock.currentThreadTimeMillis();
     }
-
-    @Override
-    public long currentThreadTimeMicro() {
-        return android.os.SystemClock.currentThreadTimeMicro();
-    }
-
-    @Override
-    public long currentTimeMicro() {
-        return android.os.SystemClock.currentTimeMicro();
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 819a7f6..c85d600 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -117,7 +117,7 @@
     }
 
     protected void waitForUiOffloadThread() {
-        Future<?> future = Dependency.get(UiOffloadThread.class).submit(() -> {});
+        Future<?> future = Dependency.get(UiOffloadThread.class).execute(() -> { });
         try {
             future.get();
         } catch (InterruptedException | ExecutionException e) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
index 3561e34..a19d1df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingManagerProxyTest.java
@@ -34,7 +34,9 @@
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.sensors.ProximitySensor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.After;
 import org.junit.Before;
@@ -55,6 +57,7 @@
     private FalsingManagerProxy mProxy;
     private DeviceConfigProxy mDeviceConfig;
     private TestableLooper mTestableLooper;
+    private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
     @Before
     public void setup() {
@@ -77,7 +80,7 @@
     @Test
     public void test_brightLineFalsingManagerDisabled() {
         mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mProximitySensor,
-                mDeviceConfig);
+                mDeviceConfig, mUiBgExecutor);
         assertThat(mProxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
     }
 
@@ -87,14 +90,14 @@
                 BRIGHTLINE_FALSING_MANAGER_ENABLED, "true", false);
         mTestableLooper.processAllMessages();
         mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mProximitySensor,
-                mDeviceConfig);
+                mDeviceConfig, mUiBgExecutor);
         assertThat(mProxy.getInternalFalsingManager(), instanceOf(BrightLineFalsingManager.class));
     }
 
     @Test
     public void test_brightLineFalsingManagerToggled() throws InterruptedException {
         mProxy = new FalsingManagerProxy(getContext(), mPluginManager, mHandler, mProximitySensor,
-                mDeviceConfig);
+                mDeviceConfig, mUiBgExecutor);
         assertThat(mProxy.getInternalFalsingManager(), instanceOf(FalsingManagerImpl.class));
 
         mDeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
index 7fa1dbe..a00cabc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/DismissCallbackRegistryTest.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT 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.keyguard;
@@ -24,6 +24,8 @@
 
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -41,10 +43,11 @@
     private DismissCallbackRegistry mDismissCallbackRegistry;
     private @Mock IKeyguardDismissCallback mMockCallback;
     private @Mock IKeyguardDismissCallback mMockCallback2;
+    private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
     @Before
     public void setUp() throws Exception {
-        mDismissCallbackRegistry =  new DismissCallbackRegistry();
+        mDismissCallbackRegistry =  new DismissCallbackRegistry(mUiBgExecutor);
         MockitoAnnotations.initMocks(this);
     }
 
@@ -52,7 +55,7 @@
     public void testCancelled() throws Exception {
         mDismissCallbackRegistry.addCallback(mMockCallback);
         mDismissCallbackRegistry.notifyDismissCancelled();
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
         verify(mMockCallback).onDismissCancelled();
     }
 
@@ -61,7 +64,7 @@
         mDismissCallbackRegistry.addCallback(mMockCallback);
         mDismissCallbackRegistry.addCallback(mMockCallback2);
         mDismissCallbackRegistry.notifyDismissCancelled();
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
         verify(mMockCallback).onDismissCancelled();
         verify(mMockCallback2).onDismissCancelled();
     }
@@ -70,7 +73,7 @@
     public void testSucceeded() throws Exception {
         mDismissCallbackRegistry.addCallback(mMockCallback);
         mDismissCallbackRegistry.notifyDismissSucceeded();
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
         verify(mMockCallback).onDismissSucceeded();
     }
 
@@ -79,7 +82,7 @@
         mDismissCallbackRegistry.addCallback(mMockCallback);
         mDismissCallbackRegistry.addCallback(mMockCallback2);
         mDismissCallbackRegistry.notifyDismissSucceeded();
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
         verify(mMockCallback).onDismissSucceeded();
         verify(mMockCallback2).onDismissSucceeded();
     }
@@ -89,7 +92,7 @@
         mDismissCallbackRegistry.addCallback(mMockCallback);
         mDismissCallbackRegistry.notifyDismissSucceeded();
         mDismissCallbackRegistry.notifyDismissSucceeded();
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
         verify(mMockCallback, times(1)).onDismissSucceeded();
     }
 }
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 a8a2b33..64fbc1b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -38,6 +38,8 @@
 import com.android.systemui.plugins.FalsingManager;
 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;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -58,6 +60,7 @@
     private @Mock StatusBarWindowController mStatusBarWindowController;
     private @Mock BroadcastDispatcher mBroadcastDispatcher;
     private @Mock DismissCallbackRegistry mDismissCallbackRegistry;
+    private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
     private FalsingManagerFake mFalsingManager;
 
@@ -75,7 +78,7 @@
             mViewMediator = new KeyguardViewMediator(
                     mContext, mFalsingManager, mLockPatternUtils, mBroadcastDispatcher,
                     mStatusBarWindowController, () -> mStatusBarKeyguardViewManager,
-                    mDismissCallbackRegistry);
+                    mDismissCallbackRegistry, mUiBgExecutor);
         });
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index a5395e8..402a99d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -17,7 +17,7 @@
 
 package com.android.systemui.statusbar;
 
-import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
 
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
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 e67aa69..bb9c14b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -26,7 +26,6 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dependency;
-import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -86,7 +85,6 @@
                 Dependency.get(NotificationLockscreenUserManager.class);
         NotificationViewHierarchyManager viewHierarchyManager =
                 Dependency.get(NotificationViewHierarchyManager.class);
-        Dependency.get(InitController.class).executePostInitTasks();
         entryManager.setUpWithPresenter(mPresenter, mListContainer, mHeadsUpManager);
         entryManager.addNotificationEntryListener(mEntryListener);
         gutsManager.setUpWithPresenter(mPresenter, mListContainer,
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 c7810be..40b0ba9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -22,6 +22,7 @@
 
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
 
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 
 import android.annotation.Nullable;
@@ -46,14 +47,17 @@
 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.NotificationContentInflater.InflationFlag;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflaterTest;
+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.tests.R;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 /**
  * A helper class to create {@link ExpandableNotificationRow} (for both individual and group
  * notifications).
@@ -325,10 +329,8 @@
         entry.setRow(row);
         entry.createIcons(mContext, entry.getSbn());
         row.setEntry(entry);
-        row.getNotificationInflater().addInflationFlags(extraInflationFlags);
-        NotificationContentInflaterTest.runThenWaitForInflation(
-                () -> row.inflateViews(),
-                row.getNotificationInflater());
+        row.setInflationFlags(extraInflationFlags);
+        inflateAndWait(row);
 
         // This would be done as part of onAsyncInflationFinished, but we skip large amounts of
         // the callback chain, so we need to make up for not adding it to the group manager
@@ -337,6 +339,28 @@
         return row;
     }
 
+    private static void inflateAndWait(ExpandableNotificationRow row) throws Exception {
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        row.getNotificationInflater().setInflateSynchronously(true);
+        NotificationContentInflater.InflationCallback callback =
+                new NotificationContentInflater.InflationCallback() {
+                    @Override
+                    public void handleInflationException(StatusBarNotification notification,
+                            Exception e) {
+                        countDownLatch.countDown();
+                    }
+
+                    @Override
+                    public void onAsyncInflationFinished(NotificationEntry entry,
+                            int inflatedFlags) {
+                        countDownLatch.countDown();
+                    }
+                };
+        row.setInflationCallback(callback);
+        row.inflateViews();
+        assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
+    }
+
     private BubbleMetadata makeBubbleMetadata(PendingIntent deleteIntent) {
         Intent target = new Intent(mContext, BubblesTestActivity.class);
         PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 1b05216..c97813d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -38,8 +38,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.Dependency;
-import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
@@ -107,7 +105,6 @@
                 mock(KeyguardBypassController.class),
                 mock(BubbleController.class),
                 mock(DynamicPrivacyController.class));
-        Dependency.get(InitController.class).executePostInitTasks();
         mViewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer);
     }
 
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 7f5105e..f55ea4f 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
@@ -62,7 +62,6 @@
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dependency;
 import com.android.systemui.ForegroundServiceController;
-import com.android.systemui.InitController;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -233,7 +232,6 @@
                         mock(PeopleNotificationIdentifier.class)),
                 mEnvironment
         );
-        Dependency.get(InitController.class).executePostInitTasks();
         mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mHeadsUpManager);
         mEntryManager.addNotificationEntryListener(mEntryListener);
         mEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
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
index 9f90396..6e9c2c8 100644
--- 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
@@ -727,7 +727,7 @@
         mListBuilder.addPreGroupFilter(filter3);
 
         // GIVEN the SystemClock is set to a particular time:
-        mSystemClock.setUptimeMillis(47);
+        mSystemClock.setUptimeMillis(10047);
 
         // WHEN the pipeline is kicked off on a list of notifs
         addNotif(0, PACKAGE_1);
@@ -735,12 +735,12 @@
         dispatchBuild();
 
         // THEN the value of `now` is the same for all calls to shouldFilterOut
-        verify(filter1).shouldFilterOut(mEntrySet.get(0), 47);
-        verify(filter2).shouldFilterOut(mEntrySet.get(0), 47);
-        verify(filter3).shouldFilterOut(mEntrySet.get(0), 47);
-        verify(filter1).shouldFilterOut(mEntrySet.get(1), 47);
-        verify(filter2).shouldFilterOut(mEntrySet.get(1), 47);
-        verify(filter3).shouldFilterOut(mEntrySet.get(1), 47);
+        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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
index 4f1ffbe..2662c80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java
@@ -29,10 +29,10 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.UiOffloadThread;
 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -52,12 +52,12 @@
     private NotificationLogger.ExpansionStateLogger mLogger;
     @Mock
     private IStatusBarService mBarService;
+    private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mLogger = new NotificationLogger.ExpansionStateLogger(
-                Dependency.get(UiOffloadThread.class));
+        mLogger = new NotificationLogger.ExpansionStateLogger(mUiBgExecutor);
         mLogger.mBarService = mBarService;
     }
 
@@ -66,7 +66,7 @@
         mLogger.onVisibilityChanged(
                 Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
                 Collections.emptyList());
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
 
         verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
                 eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt());
@@ -76,7 +76,7 @@
     public void testExpanded() throws RemoteException {
         mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
                 NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
 
         verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
                 eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt());
@@ -89,7 +89,7 @@
         mLogger.onVisibilityChanged(
                 Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
                 Collections.emptyList());
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
 
         verify(mBarService, Mockito.never()).onNotificationExpansionChanged(
                 eq(NOTIFICATION_KEY), anyBoolean(), anyBoolean(), anyInt());
@@ -102,7 +102,7 @@
         mLogger.onVisibilityChanged(
                 Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
                 Collections.emptyList());
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
 
         verify(mBarService).onNotificationExpansionChanged(
                 NOTIFICATION_KEY, true, true,
@@ -117,7 +117,7 @@
                 Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true,
                     NotificationVisibility.NotificationLocation.LOCATION_MAIN_AREA)),
                 Collections.emptyList());
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
 
         verify(mBarService).onNotificationExpansionChanged(
                 NOTIFICATION_KEY, false, true,
@@ -133,7 +133,7 @@
                 Collections.emptyList());
         mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
                 NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
 
         verify(mBarService).onNotificationExpansionChanged(
                 NOTIFICATION_KEY, false, true,
@@ -150,7 +150,7 @@
                 NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
         mLogger.onExpansionChanged(NOTIFICATION_KEY, false, true,
                 NotificationVisibility.NotificationLocation.LOCATION_UNKNOWN);
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
 
         verify(mBarService).onNotificationExpansionChanged(
                 NOTIFICATION_KEY, false, true,
@@ -164,7 +164,7 @@
         mLogger.onVisibilityChanged(
                 Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
                 Collections.emptyList());
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
         verify(mBarService).onNotificationExpansionChanged(
                 NOTIFICATION_KEY, true, true, ExpandableViewState.LOCATION_UNKNOWN);
 
@@ -172,7 +172,7 @@
         mLogger.onVisibilityChanged(
                 Collections.singletonList(createNotificationVisibility(NOTIFICATION_KEY, true)),
                 Collections.emptyList());
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
         // onNotificationExpansionChanged is called the second time.
         verify(mBarService, times(2)).onNotificationExpansionChanged(
                 NOTIFICATION_KEY, true, true, ExpandableViewState.LOCATION_UNKNOWN);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index e23d0ae..d826ce1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT 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.notification.logging;
@@ -36,9 +36,7 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.Dependency;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.UiOffloadThread;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -47,6 +45,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import com.google.android.collect.Lists;
 
@@ -60,6 +60,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -82,6 +83,7 @@
     private TestableNotificationLogger mLogger;
     private NotificationEntryListener mNotificationEntryListener;
     private ConcurrentLinkedQueue<AssertionError> mErrorQueue = new ConcurrentLinkedQueue<>();
+    private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
     @Before
     public void setUp() {
@@ -98,7 +100,7 @@
                 .build();
         mEntry.setRow(mRow);
 
-        mLogger = new TestableNotificationLogger(mListener, Dependency.get(UiOffloadThread.class),
+        mLogger = new TestableNotificationLogger(mListener, mUiBgExecutor,
                 mEntryManager, mock(StatusBarStateControllerImpl.class), mBarService,
                 mExpansionStateLogger);
         mLogger.setUpWithContainer(mListContainer);
@@ -130,7 +132,7 @@
         when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
         mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         TestableLooper.get(this).processAllMessages();
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
 
         if(!mErrorQueue.isEmpty()) {
             throw mErrorQueue.poll();
@@ -140,7 +142,7 @@
         Mockito.reset(mBarService);
         mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         TestableLooper.get(this).processAllMessages();
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
 
         verify(mBarService, never()).onNotificationVisibilityChanged(any(), any());
     }
@@ -152,11 +154,11 @@
         when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
         mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         TestableLooper.get(this).processAllMessages();
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
         Mockito.reset(mBarService);
 
         mLogger.stopNotificationLogging();
-        waitForUiOffloadThread();
+        mUiBgExecutor.runAllReady();
         // The visibility objects are recycled by NotificationLogger, so we can't use specific
         // matchers here.
         verify(mBarService, times(1)).onNotificationVisibilityChanged(any(), any());
@@ -165,12 +167,12 @@
     private class TestableNotificationLogger extends NotificationLogger {
 
         TestableNotificationLogger(NotificationListener notificationListener,
-                UiOffloadThread uiOffloadThread,
+                Executor uiBgExecutor,
                 NotificationEntryManager entryManager,
                 StatusBarStateControllerImpl statusBarStateController,
                 IStatusBarService barService,
                 ExpansionStateLogger expansionStateLogger) {
-            super(notificationListener, uiOffloadThread, entryManager, statusBarStateController,
+            super(notificationListener, uiBgExecutor, entryManager, statusBarStateController,
                     expansionStateLogger);
             mBarService = barService;
             // Make this on the current thread so we can wait for it during tests.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index d17c573..3d79ce1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -19,9 +19,9 @@
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
-import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_ALL;
-import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_HEADS_UP;
-import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_PUBLIC;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -152,7 +152,7 @@
 
         row.setNeedsRedaction(true);
 
-        assertTrue(row.getNotificationInflater().isInflationFlagSet(FLAG_CONTENT_VIEW_PUBLIC));
+        assertTrue(row.isInflationFlagSet(FLAG_CONTENT_VIEW_PUBLIC));
     }
 
     @Test
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 c7e59ef..dc4e498 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
@@ -16,10 +16,10 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_ALL;
-import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_EXPANDED;
-import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_HEADS_UP;
-import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_PUBLIC;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
+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 com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -48,7 +48,9 @@
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationCallback;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.tests.R;
 
 import org.junit.Assert;
@@ -82,25 +84,17 @@
         ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
                 mBuilder.build());
         mRow = spy(row);
-        mNotificationInflater = new NotificationContentInflater(mRow);
-        mNotificationInflater.setInflationCallback(new InflationCallback() {
-            @Override
-            public void handleInflationException(StatusBarNotification notification,
-                    Exception e) {
-            }
-
-            @Override
-            public void onAsyncInflationFinished(NotificationEntry entry,
-                    @NotificationContentInflater.InflationFlag int inflatedFlags) {
-            }
-        });
+        mNotificationInflater = new NotificationContentInflater();
     }
 
     @Test
     public void testIncreasedHeadsUpBeingUsed() {
-        mNotificationInflater.setUsesIncreasedHeadsUpHeight(true);
+        BindParams params = new BindParams();
+        params.usesIncreasedHeadsUpHeight = true;
         Notification.Builder builder = spy(mBuilder);
-        mNotificationInflater.inflateNotificationViews(
+        mNotificationInflater.inflateNotificationViews(mRow.getEntry(),
+                mRow,
+                params,
                 true /* inflateSynchronously */,
                 FLAG_CONTENT_VIEW_ALL,
                 builder,
@@ -110,9 +104,12 @@
 
     @Test
     public void testIncreasedHeightBeingUsed() {
-        mNotificationInflater.setUsesIncreasedHeight(true);
+        BindParams params = new BindParams();
+        params.usesIncreasedHeight = true;
         Notification.Builder builder = spy(mBuilder);
-        mNotificationInflater.inflateNotificationViews(
+        mNotificationInflater.inflateNotificationViews(mRow.getEntry(),
+                mRow,
+                params,
                 true /* inflateSynchronously */,
                 FLAG_CONTENT_VIEW_ALL,
                 builder,
@@ -122,17 +119,13 @@
 
     @Test
     public void testInflationCallsUpdated() throws Exception {
-        runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(),
-                mNotificationInflater);
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
         verify(mRow).onNotificationUpdated();
     }
 
     @Test
     public void testInflationOnlyInflatesSetFlags() throws Exception {
-        mNotificationInflater.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP,
-                true /* shouldInflate */);
-        runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(),
-                mNotificationInflater);
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_HEADS_UP, mRow);
 
         assertNotNull(mRow.getPrivateLayout().getHeadsUpChild());
         verify(mRow).onNotificationUpdated();
@@ -143,8 +136,8 @@
         mRow.getPrivateLayout().removeAllViews();
         mRow.getEntry().getSbn().getNotification().contentView
                 = new RemoteViews(mContext.getPackageName(), R.layout.status_bar);
-        runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(),
-                true /* expectingException */, mNotificationInflater);
+        inflateAndWait(true /* expectingException */, mNotificationInflater, FLAG_CONTENT_VIEW_ALL,
+                mRow);
         assertTrue(mRow.getPrivateLayout().getChildCount() == 0);
         verify(mRow, times(0)).onNotificationUpdated();
     }
@@ -152,8 +145,7 @@
     @Test
     public void testAsyncTaskRemoved() throws Exception {
         mRow.getEntry().abortTask();
-        runThenWaitForInflation(() -> mNotificationInflater.inflateNotificationViews(),
-                mNotificationInflater);
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
         verify(mRow).onNotificationUpdated();
     }
 
@@ -161,7 +153,13 @@
     public void testRemovedNotInflated() throws Exception {
         mRow.setRemoved();
         mNotificationInflater.setInflateSynchronously(true);
-        mNotificationInflater.inflateNotificationViews();
+        mNotificationInflater.bindContent(
+                mRow.getEntry(),
+                mRow,
+                FLAG_CONTENT_VIEW_ALL,
+                new BindParams(),
+                false /* forceInflate */,
+                null /* callback */);
         Assert.assertNull(mRow.getEntry().getRunningTask());
     }
 
@@ -189,7 +187,7 @@
 
                     @Override
                     public void onAsyncInflationFinished(NotificationEntry entry,
-                            @NotificationContentInflater.InflationFlag int inflatedFlags) {
+                            @InflationFlag int inflatedFlags) {
                         countDownLatch.countDown();
                     }
                 }, mRow.getPrivateLayout(), null, null, new HashMap<>(),
@@ -207,26 +205,25 @@
         assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
     }
 
-    @Test
-    public void testUpdateNeedsRedactionReinflatesChangedContentViews() {
-        mNotificationInflater.updateInflationFlag(FLAG_CONTENT_VIEW_PUBLIC, true);
-        mNotificationInflater.updateNeedsRedaction(true);
-
-        NotificationContentInflater.AsyncInflationTask asyncInflationTask =
-                (NotificationContentInflater.AsyncInflationTask) mRow.getEntry().getRunningTask();
-        assertEquals(FLAG_CONTENT_VIEW_PUBLIC, asyncInflationTask.getReInflateFlags());
-        asyncInflationTask.abort();
-    }
-
     /* Cancelling requires us to be on the UI thread otherwise we might have a race */
     @Test
     public void testSupersedesExistingTask() {
-        mNotificationInflater.addInflationFlags(FLAG_CONTENT_VIEW_ALL);
-        mNotificationInflater.inflateNotificationViews();
+        mNotificationInflater.bindContent(
+                mRow.getEntry(),
+                mRow,
+                FLAG_CONTENT_VIEW_ALL,
+                new BindParams(),
+                false /* forceInflate */,
+                null /* callback */);
 
-        // Trigger inflation of content and expanded only.
-        mNotificationInflater.setIsLowPriority(true);
-        mNotificationInflater.setIsChildInGroup(true);
+        // Trigger inflation of contracted only.
+        mNotificationInflater.bindContent(
+                mRow.getEntry(),
+                mRow,
+                FLAG_CONTENT_VIEW_CONTRACTED,
+                new BindParams(),
+                false /* forceInflate */,
+                null /* callback */);
 
         InflationTask runningTask = mRow.getEntry().getRunningTask();
         NotificationContentInflater.AsyncInflationTask asyncInflationTask =
@@ -248,17 +245,21 @@
                 NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView));
     }
 
-    public static void runThenWaitForInflation(Runnable block,
-            NotificationContentInflater inflater) throws Exception {
-        runThenWaitForInflation(block, false /* expectingException */, inflater);
+    private static void inflateAndWait(NotificationContentInflater inflater,
+            @InflationFlag int contentToInflate,
+            ExpandableNotificationRow row)
+            throws Exception {
+        inflateAndWait(false /* expectingException */, inflater, contentToInflate, row);
     }
 
-    private static void runThenWaitForInflation(Runnable block, boolean expectingException,
-            NotificationContentInflater inflater) throws Exception {
+    private static void inflateAndWait(boolean expectingException,
+            NotificationContentInflater inflater,
+            @InflationFlag int contentToInflate,
+            ExpandableNotificationRow row) throws Exception {
         CountDownLatch countDownLatch = new CountDownLatch(1);
         final ExceptionHolder exceptionHolder = new ExceptionHolder();
         inflater.setInflateSynchronously(true);
-        inflater.setInflationCallback(new InflationCallback() {
+        InflationCallback callback = new InflationCallback() {
             @Override
             public void handleInflationException(StatusBarNotification notification,
                     Exception e) {
@@ -270,15 +271,21 @@
 
             @Override
             public void onAsyncInflationFinished(NotificationEntry entry,
-                    @NotificationContentInflater.InflationFlag int inflatedFlags) {
+                    @InflationFlag int inflatedFlags) {
                 if (expectingException) {
                     exceptionHolder.setException(new RuntimeException(
                             "Inflation finished even though there should be an error"));
                 }
                 countDownLatch.countDown();
             }
-        });
-        block.run();
+        };
+        inflater.bindContent(
+                row.getEntry(),
+                row,
+                contentToInflate,
+                new BindParams(),
+                false /* forceInflate */,
+                callback /* callback */);
         assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
         if (exceptionHolder.mException != null) {
             throw exceptionHolder.mException;
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 deca51f..77a6a26 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
@@ -47,9 +47,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.systemui.ActivityStarterDelegate;
-import com.android.systemui.Dependency;
 import com.android.systemui.ExpandHelper;
-import com.android.systemui.InitController;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -170,7 +168,6 @@
                 ),
                 mock(NotificationEntryManager.KeyguardEnvironment.class));
         mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
-        Dependency.get(InitController.class).executePostInitTasks();
         mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager);
 
 
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 532192b..86b2a44 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
@@ -72,6 +72,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -126,6 +128,7 @@
     private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
     @Mock
     private NotificationPanelView mNotificationPanelView;
+    private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
     private NotificationTestHelper mNotificationTestHelper;
     private ExpandableNotificationRow mNotificationRow;
@@ -178,8 +181,9 @@
                 mock(NotificationLockscreenUserManager.class),
                 mKeyguardStateController,
                 mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class),
-                mock(LockPatternUtils.class), mHandler, mHandler, mActivityIntentHelper,
-                mBubbleController, mShadeController, mSuperStatusBarViewFactory))
+                mock(LockPatternUtils.class), mHandler, mHandler, mUiBgExecutor,
+                mActivityIntentHelper, mBubbleController, mShadeController,
+                mSuperStatusBarViewFactory))
                 .setStatusBar(mStatusBar)
                 .setNotificationPresenter(mock(NotificationPresenter.class))
                 .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class))
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 f6ed4e6..1296a97 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
@@ -77,6 +77,7 @@
     private FakeMetricsLogger mMetricsLogger;
     private ShadeController mShadeController = mock(ShadeController.class);
     private StatusBar mStatusBar = mock(StatusBar.class);
+    private InitController mInitController = new InitController();
 
     @Before
     public void setup() {
@@ -100,7 +101,6 @@
         mDependency.injectMockDependency(VisualStabilityManager.class);
         mDependency.injectMockDependency(NotificationGutsManager.class);
         mDependency.injectMockDependency(StatusBarWindowController.class);
-        mDependency.injectMockDependency(InitController.class);
         NotificationEntryManager entryManager =
                 mDependency.injectMockDependency(NotificationEntryManager.class);
         when(entryManager.getActiveNotificationsForCurrentUser()).thenReturn(new ArrayList<>());
@@ -115,7 +115,7 @@
                 mock(NotificationAlertingManager.class),
                 mock(NotificationRowBinderImpl.class), mock(KeyguardStateController.class),
                 mock(KeyguardIndicationController.class),
-                mStatusBar, mock(ShadeControllerImpl.class), mCommandQueue);
+                mStatusBar, mock(ShadeControllerImpl.class), mCommandQueue, mInitController);
     }
 
     @Test
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 d3ae7a7..61dd2aa 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
@@ -74,11 +74,9 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.ViewMediatorCallback;
-import com.android.systemui.Dependency;
 import com.android.systemui.InitController;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.UiOffloadThread;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
@@ -89,6 +87,7 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.ScreenPinningRequest;
@@ -133,6 +132,8 @@
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.volume.VolumeComponent;
 
 import org.junit.Before;
@@ -244,7 +245,10 @@
     @Mock private LockscreenLockIconController mLockscreenLockIconController;
     @Mock private StatusBarNotificationActivityStarter.Builder
             mStatusBarNotificationActivityStarterBuilder;
+    @Mock private DarkIconDispatcher mDarkIconDispatcher;
     private ShadeController mShadeController;
+    private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
+    private InitController mInitController = new InitController();
 
     @Before
     public void setup() throws Exception {
@@ -266,7 +270,7 @@
 
         mMetricsLogger = new FakeMetricsLogger();
         NotificationLogger notificationLogger = new NotificationLogger(mNotificationListener,
-                Dependency.get(UiOffloadThread.class), mEntryManager, mStatusBarStateController,
+                mUiBgExecutor, mEntryManager, mStatusBarStateController,
                 mExpansionStateLogger);
         notificationLogger.setVisibilityReporter(mock(Runnable.class));
 
@@ -349,7 +353,7 @@
                 mNotificationAlertingManager,
                 new DisplayMetrics(),
                 mMetricsLogger,
-                Dependency.get(UiOffloadThread.class),
+                mUiBgExecutor,
                 mNotificationMediaManager,
                 mLockscreenUserManager,
                 mRemoteInputManager,
@@ -393,6 +397,9 @@
                 mSuperStatusBarViewFactory,
                 mStatusBarKeyguardViewManager,
                 mViewMediatorCallback,
+                mInitController,
+                mDarkIconDispatcher,
+                new Handler(TestableLooper.get(this).getLooper()),
                 mDismissCallbackRegistry);
 
         when(mStatusBarWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
@@ -417,7 +424,7 @@
         mStatusBar.mBarService = mBarService;
         mStatusBar.mStackScroller = mStackScroller;
         mStatusBar.startKeyguard();
-        Dependency.get(InitController.class).executePostInitTasks();
+        mInitController.executePostInitTasks();
         notificationLogger.setUpWithContainer(mStackScroller);
     }
 
@@ -641,7 +648,7 @@
     public void testLogHidden() {
         try {
             mStatusBar.handleVisibleToUserChanged(false);
-            waitForUiOffloadThread();
+            mUiBgExecutor.runAllReady();
             verify(mBarService, times(1)).onPanelHidden();
             verify(mBarService, never()).onPanelRevealed(anyBoolean(), anyInt());
         } catch (RemoteException e) {
@@ -659,7 +666,7 @@
 
         try {
             mStatusBar.handleVisibleToUserChanged(true);
-            waitForUiOffloadThread();
+            mUiBgExecutor.runAllReady();
             verify(mBarService, never()).onPanelHidden();
             verify(mBarService, times(1)).onPanelRevealed(false, 1);
         } catch (RemoteException e) {
@@ -678,7 +685,7 @@
 
         try {
             mStatusBar.handleVisibleToUserChanged(true);
-            waitForUiOffloadThread();
+            mUiBgExecutor.runAllReady();
             verify(mBarService, never()).onPanelHidden();
             verify(mBarService, times(1)).onPanelRevealed(true, 5);
         } catch (RemoteException e) {
@@ -696,7 +703,7 @@
 
         try {
             mStatusBar.handleVisibleToUserChanged(true);
-            waitForUiOffloadThread();
+            mUiBgExecutor.runAllReady();
             verify(mBarService, never()).onPanelHidden();
             verify(mBarService, times(1)).onPanelRevealed(false, 5);
         } catch (RemoteException e) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
index f3c0530..7c7ad53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java
@@ -17,7 +17,6 @@
 package com.android.systemui.util.concurrency;
 
 import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.util.time.FakeSystemClock.ClockTickListener;
 
 import java.util.Collections;
 import java.util.PriorityQueue;
@@ -29,15 +28,6 @@
     private PriorityQueue<QueuedRunnable> mQueuedRunnables = new PriorityQueue<>();
     private boolean mIgnoreClockUpdates;
 
-    private ClockTickListener mClockTickListener = new ClockTickListener() {
-        @Override
-        public void onUptimeMillis(long uptimeMillis) {
-            if (!mIgnoreClockUpdates) {
-                runAllReady();
-            }
-        }
-    };
-
     /**
      * Initializes a fake executor.
      *
@@ -47,7 +37,11 @@
      */
     public FakeExecutor(FakeSystemClock clock) {
         mClock = clock;
-        mClock.addListener(mClockTickListener);
+        mClock.addListener(() -> {
+            if (!mIgnoreClockUpdates) {
+                runAllReady();
+            }
+        });
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
index bd64124..abc283f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java
@@ -52,28 +52,28 @@
         FakeExecutor fakeExecutor = new FakeExecutor(clock);
         RunnableImpl runnable = new RunnableImpl();
 
-        assertEquals(0, clock.uptimeMillis());
+        assertEquals(10000, clock.uptimeMillis());
         assertEquals(0, runnable.mRunCount);
 
         // Execute two runnables. They should not run and should be left pending.
         fakeExecutor.execute(runnable);
         assertEquals(0, runnable.mRunCount);
-        assertEquals(0, clock.uptimeMillis());
+        assertEquals(10000, clock.uptimeMillis());
         assertEquals(1, fakeExecutor.numPending());
         fakeExecutor.execute(runnable);
         assertEquals(0, runnable.mRunCount);
-        assertEquals(0, clock.uptimeMillis());
+        assertEquals(10000, clock.uptimeMillis());
         assertEquals(2, fakeExecutor.numPending());
 
         // Run one pending runnable.
         assertTrue(fakeExecutor.runNextReady());
         assertEquals(1, runnable.mRunCount);
-        assertEquals(0, clock.uptimeMillis());
+        assertEquals(10000, clock.uptimeMillis());
         assertEquals(1, fakeExecutor.numPending());
         // Run a second pending runnable.
         assertTrue(fakeExecutor.runNextReady());
         assertEquals(2, runnable.mRunCount);
-        assertEquals(0, clock.uptimeMillis());
+        assertEquals(10000, clock.uptimeMillis());
         assertEquals(0, fakeExecutor.numPending());
 
         // No more runnables to run.
@@ -83,12 +83,12 @@
         fakeExecutor.execute(runnable);
         fakeExecutor.execute(runnable);
         assertEquals(2, runnable.mRunCount);
-        assertEquals(0, clock.uptimeMillis());
+        assertEquals(10000, clock.uptimeMillis());
         assertEquals(2, fakeExecutor.numPending());
         // Execute all pending runnables in batch.
         assertEquals(2, fakeExecutor.runAllReady());
         assertEquals(4, runnable.mRunCount);
-        assertEquals(0, clock.uptimeMillis());
+        assertEquals(10000, clock.uptimeMillis());
         assertEquals(0, fakeExecutor.runAllReady());
     }
 
@@ -106,7 +106,7 @@
         fakeExecutor.executeDelayed(runnable, 50);
         fakeExecutor.executeDelayed(runnable, 100);
         assertEquals(0, runnable.mRunCount);
-        assertEquals(0, clock.uptimeMillis());
+        assertEquals(10000, clock.uptimeMillis());
         assertEquals(3, fakeExecutor.numPending());
         // Delayed runnables should not advance the clock and therefore should not run.
         assertFalse(fakeExecutor.runNextReady());
@@ -140,7 +140,7 @@
         fakeExecutor.executeDelayed(runnable, 50);
         fakeExecutor.executeDelayed(runnable, 100);
         assertEquals(0, runnable.mRunCount);
-        assertEquals(0, clock.uptimeMillis());
+        assertEquals(10000, clock.uptimeMillis());
         assertEquals(3, fakeExecutor.numPending());
         // Delayed runnables should not advance the clock and therefore should not run.
         assertFalse(fakeExecutor.runNextReady());
@@ -150,24 +150,24 @@
         // Advance the clock to the next runnable. Check that it is run.
         assertEquals(1, fakeExecutor.advanceClockToNext());
         assertEquals(1, fakeExecutor.runAllReady());
-        assertEquals(1, clock.uptimeMillis());
+        assertEquals(10001, clock.uptimeMillis());
         assertEquals(2, fakeExecutor.numPending());
         assertEquals(1, runnable.mRunCount);
         assertEquals(49, fakeExecutor.advanceClockToNext());
         assertEquals(1, fakeExecutor.runAllReady());
-        assertEquals(50, clock.uptimeMillis());
+        assertEquals(10050, clock.uptimeMillis());
         assertEquals(1, fakeExecutor.numPending());
         assertEquals(2, runnable.mRunCount);
         assertEquals(50, fakeExecutor.advanceClockToNext());
         assertEquals(1, fakeExecutor.runAllReady());
-        assertEquals(100, clock.uptimeMillis());
+        assertEquals(10100, clock.uptimeMillis());
         assertEquals(0, fakeExecutor.numPending());
         assertEquals(3, runnable.mRunCount);
 
         // Nothing left to do
         assertEquals(0, fakeExecutor.advanceClockToNext());
         assertEquals(0, fakeExecutor.runAllReady());
-        assertEquals(100, clock.uptimeMillis());
+        assertEquals(10100, clock.uptimeMillis());
         assertEquals(0, fakeExecutor.numPending());
         assertEquals(3, runnable.mRunCount);
     }
@@ -193,7 +193,7 @@
                     return null;
                 };
 
-        assertEquals(0, clock.uptimeMillis());
+        assertEquals(10000, clock.uptimeMillis());
         checkRunCounts.invoke(0, 0, 0, 0);
 
         fakeExecutor.execute(runnableA);
@@ -227,8 +227,8 @@
 
         fakeExecutor.execute(runnableA);
         fakeExecutor.executeAtTime(runnableB, 0);  // this is in the past!
-        fakeExecutor.executeAtTime(runnableC, 1000);
-        fakeExecutor.executeAtTime(runnableD, 500);
+        fakeExecutor.executeAtTime(runnableC, 11000);
+        fakeExecutor.executeAtTime(runnableD, 10500);
 
         fakeExecutor.advanceClockToNext();
         fakeExecutor.runAllReady();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java b/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
index e94eaaf..601f88e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/time/FakeSystemClock.java
@@ -16,94 +16,70 @@
 
 package com.android.systemui.util.time;
 
+import com.android.systemui.util.concurrency.FakeExecutor;
+
 import java.util.ArrayList;
 import java.util.List;
 
+/**
+ * A fake {@link SystemClock} for use with {@link FakeExecutor}.
+ *
+ * Attempts to simulate the behavior of a real system clock. Time can be moved forward but not
+ * backwards. uptimeMillis, elapsedRealtime, and currentThreadTimeMillis are all kept in sync.
+ *
+ * Unless otherwise specified, uptimeMillis and elapsedRealtime will advance the same amount with
+ * every call to {@link #advanceTime(long)}. Thread time always lags by 50% of the uptime
+ * advancement to simulate time loss due to scheduling.
+ */
 public class FakeSystemClock implements SystemClock {
-    private long mUptimeMillis;
-    private long mElapsedRealtime;
-    private long mElapsedRealtimeNanos;
-    private long mCurrentThreadTimeMillis;
-    private long mCurrentThreadTimeMicro;
-    private long mCurrentTimeMicro;
+    private long mUptimeMillis = 10000;
+    private long mElapsedRealtime = 10000;
+    private long mCurrentThreadTimeMillis = 10000;
 
-    List<ClockTickListener> mListeners = new ArrayList<>();
+    private final List<ClockTickListener> mListeners = new ArrayList<>();
 
     @Override
     public long uptimeMillis() {
-        long value = mUptimeMillis;
-        return value;
+        return mUptimeMillis;
     }
 
     @Override
     public long elapsedRealtime() {
-        long value = mElapsedRealtime;
-        return value;
+        return mElapsedRealtime;
     }
 
     @Override
     public long elapsedRealtimeNanos() {
-        long value = mElapsedRealtimeNanos;
-        return value;
+        return mElapsedRealtime * 1000000 + 447;
     }
 
     @Override
     public long currentThreadTimeMillis() {
-        long value = mCurrentThreadTimeMillis;
-        return value;
+        return mCurrentThreadTimeMillis;
     }
 
-    @Override
-    public long currentThreadTimeMicro() {
-        long value = mCurrentThreadTimeMicro;
-        return value;
+    public void setUptimeMillis(long uptime) {
+        advanceTime(uptime - mUptimeMillis);
     }
 
-    @Override
-    public long currentTimeMicro() {
-        long value = mCurrentTimeMicro;
-        return value;
+    public void advanceTime(long uptime) {
+        advanceTime(uptime, 0);
     }
 
-    public void setUptimeMillis(long uptimeMillis) {
-        mUptimeMillis = uptimeMillis;
-        for (ClockTickListener listener : mListeners) {
-            listener.onUptimeMillis(mUptimeMillis);
+    public void advanceTime(long uptime, long sleepTime) {
+        if (uptime < 0 || sleepTime < 0) {
+            throw new IllegalArgumentException("Time cannot go backwards");
         }
-    }
 
-    public void setElapsedRealtime(long elapsedRealtime) {
-        mElapsedRealtime = elapsedRealtime;
-        for (ClockTickListener listener : mListeners) {
-            listener.onElapsedRealtime(mElapsedRealtime);
-        }
-    }
+        if (uptime > 0 || sleepTime > 0) {
+            mUptimeMillis += uptime;
+            mElapsedRealtime += uptime + sleepTime;
 
-    public void setElapsedRealtimeNanos(long elapsedRealtimeNanos) {
-        mElapsedRealtimeNanos = elapsedRealtimeNanos;
-        for (ClockTickListener listener : mListeners) {
-            listener.onElapsedRealtimeNanos(mElapsedRealtimeNanos);
-        }
-    }
+            mCurrentThreadTimeMillis += Math.ceil(uptime * 0.5);
 
-    public void setCurrentThreadTimeMillis(long currentThreadTimeMillis) {
-        mCurrentThreadTimeMillis = currentThreadTimeMillis;
-        for (ClockTickListener listener : mListeners) {
-            listener.onCurrentThreadTimeMillis(mCurrentThreadTimeMillis);
-        }
-    }
-
-    public void setCurrentThreadTimeMicro(long currentThreadTimeMicro) {
-        mCurrentThreadTimeMicro = currentThreadTimeMicro;
-        for (ClockTickListener listener : mListeners) {
-            listener.onCurrentThreadTimeMicro(mCurrentThreadTimeMicro);
-        }
-    }
-
-    public void setCurrentTimeMicro(long currentTimeMicro) {
-        mCurrentTimeMicro = currentTimeMicro;
-        for (ClockTickListener listener : mListeners) {
-            listener.onCurrentTimeMicro(mCurrentTimeMicro);
+            for (ClockTickListener listener : mListeners) {
+                listener.onClockTick();
+            }
         }
     }
 
@@ -115,30 +91,9 @@
         mListeners.remove(listener);
     }
 
-    /** Alert all the listeners about the current time. */
-    public void synchronizeListeners() {
-        for (ClockTickListener listener : mListeners) {
-            listener.onUptimeMillis(mUptimeMillis);
-            listener.onElapsedRealtime(mElapsedRealtime);
-            listener.onElapsedRealtimeNanos(mElapsedRealtimeNanos);
-            listener.onCurrentThreadTimeMillis(mCurrentThreadTimeMillis);
-            listener.onCurrentThreadTimeMicro(mCurrentThreadTimeMicro);
-            listener.onCurrentTimeMicro(mCurrentTimeMicro);
-        }
-    }
-
-
     public interface ClockTickListener {
-        default void onUptimeMillis(long uptimeMillis) {}
-
-        default void onElapsedRealtime(long elapsedRealtime) {}
-
-        default void onElapsedRealtimeNanos(long elapsedRealtimeNanos) {}
-
-        default void onCurrentThreadTimeMillis(long currentThreadTimeMillis) {}
-
-        default void onCurrentThreadTimeMicro(long currentThreadTimeMicro) {}
-
-        default void onCurrentTimeMicro(long currentTimeMicro) {}
+        void onClockTick();
     }
+
+    private static final long START_TIME = 10000;
 }
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java b/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
index edfe3ca..9305414 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
@@ -21,7 +21,6 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
-import android.net.NetworkState;
 import android.net.RouteInfo;
 import android.net.ip.IpServer;
 import android.net.util.NetworkConstants;
@@ -72,7 +71,7 @@
     private final LinkedList<Downstream> mActiveDownstreams;
     private final byte[] mUniqueLocalPrefix;
     private short mNextSubnetId;
-    private NetworkState mUpstreamNetworkState;
+    private UpstreamNetworkState mUpstreamNetworkState;
 
     public IPv6TetheringCoordinator(ArrayList<IpServer> notifyList, SharedLog log) {
         mNotifyList = notifyList;
@@ -115,11 +114,11 @@
     }
 
     /**
-     * Call when upstream NetworkState may be changed.
-     * If upstream has ipv6 for tethering, update this new NetworkState
+     * Call when UpstreamNetworkState may be changed.
+     * If upstream has ipv6 for tethering, update this new UpstreamNetworkState
      * to IpServer. Otherwise stop ipv6 tethering on downstream interfaces.
      */
-    public void updateUpstreamNetworkState(NetworkState ns) {
+    public void updateUpstreamNetworkState(UpstreamNetworkState ns) {
         if (VDBG) {
             Log.d(TAG, "updateUpstreamNetworkState: " + toDebugString(ns));
         }
@@ -144,18 +143,15 @@
         }
     }
 
-    private void setUpstreamNetworkState(NetworkState ns) {
+    private void setUpstreamNetworkState(UpstreamNetworkState ns) {
         if (ns == null) {
             mUpstreamNetworkState = null;
         } else {
             // Make a deep copy of the parts we need.
-            mUpstreamNetworkState = new NetworkState(
-                    null,
+            mUpstreamNetworkState = new UpstreamNetworkState(
                     new LinkProperties(ns.linkProperties),
                     new NetworkCapabilities(ns.networkCapabilities),
-                    new Network(ns.network),
-                    null,
-                    null);
+                    new Network(ns.network));
         }
 
         mLog.log("setUpstreamNetworkState: " + toDebugString(mUpstreamNetworkState));
@@ -295,14 +291,11 @@
         return in6addr;
     }
 
-    private static String toDebugString(NetworkState ns) {
+    private static String toDebugString(UpstreamNetworkState ns) {
         if (ns == null) {
-            return "NetworkState{null}";
+            return "UpstreamNetworkState{null}";
         }
-        return String.format("NetworkState{%s, %s, %s}",
-                ns.network,
-                ns.networkCapabilities,
-                ns.linkProperties);
+        return ns.toString();
     }
 
     private static void stopIPv6TetheringOn(IpServer ipServer) {
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 a68b9b2..3d414ee 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -70,7 +70,6 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkInfo;
-import android.net.NetworkState;
 import android.net.NetworkUtils;
 import android.net.TetherStatesParcel;
 import android.net.TetheringConfigurationParcel;
@@ -1152,7 +1151,7 @@
 
     // Needed because the canonical source of upstream truth is just the
     // upstream interface set, |mCurrentUpstreamIfaceSet|.
-    private boolean pertainsToCurrentUpstream(NetworkState ns) {
+    private boolean pertainsToCurrentUpstream(UpstreamNetworkState ns) {
         if (ns != null && ns.linkProperties != null && mCurrentUpstreamIfaceSet != null) {
             for (String ifname : ns.linkProperties.getAllInterfaceNames()) {
                 if (mCurrentUpstreamIfaceSet.ifnames.contains(ifname)) {
@@ -1320,7 +1319,7 @@
             maybeDunSettingChanged();
 
             final TetheringConfiguration config = mConfig;
-            final NetworkState ns = (config.chooseUpstreamAutomatically)
+            final UpstreamNetworkState ns = (config.chooseUpstreamAutomatically)
                     ? mUpstreamNetworkMonitor.getCurrentPreferredUpstream()
                     : mUpstreamNetworkMonitor.selectPreferredUpstreamType(
                             config.preferredUpstreamIfaceTypes);
@@ -1341,7 +1340,7 @@
             }
         }
 
-        protected void setUpstreamNetwork(NetworkState ns) {
+        protected void setUpstreamNetwork(UpstreamNetworkState ns) {
             InterfaceSet ifaces = null;
             if (ns != null) {
                 // Find the interface with the default IPv4 route. It may be the
@@ -1357,7 +1356,7 @@
             }
             notifyDownstreamsOfNewUpstreamIface(ifaces);
             if (ns != null && pertainsToCurrentUpstream(ns)) {
-                // If we already have NetworkState for this network update it immediately.
+                // If we already have UpstreamNetworkState for this network update it immediately.
                 handleNewUpstreamNetworkState(ns);
             } else if (mCurrentUpstreamIfaceSet == null) {
                 // There are no available upstream networks.
@@ -1394,7 +1393,7 @@
             }
         }
 
-        protected void handleNewUpstreamNetworkState(NetworkState ns) {
+        protected void handleNewUpstreamNetworkState(UpstreamNetworkState ns) {
             mIPv6TetheringCoordinator.updateUpstreamNetworkState(ns);
             mOffload.updateUpstreamNetworkState(ns);
         }
@@ -1458,7 +1457,7 @@
                 return;
             }
 
-            final NetworkState ns = (NetworkState) o;
+            final UpstreamNetworkState ns = (UpstreamNetworkState) o;
 
             if (ns == null || !pertainsToCurrentUpstream(ns)) {
                 // TODO: In future, this is where upstream evaluation and selection
@@ -1728,7 +1727,7 @@
                 mOffloadController.stop();
             }
 
-            public void updateUpstreamNetworkState(NetworkState ns) {
+            public void updateUpstreamNetworkState(UpstreamNetworkState ns) {
                 mOffloadController.setUpstreamLinkProperties(
                         (ns != null) ? ns.linkProperties : null);
             }
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 0ef3805..6334c20 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
-import android.net.NetworkState;
 import android.net.RouteInfo;
 import android.net.util.InterfaceSet;
 
@@ -35,7 +34,7 @@
      * 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.
      */
-    public static @Nullable InterfaceSet getTetheringInterfaces(NetworkState ns) {
+    public static @Nullable InterfaceSet getTetheringInterfaces(UpstreamNetworkState ns) {
         if (ns == null) {
             return null;
         }
@@ -51,7 +50,7 @@
      * Get the upstream interface for IPv6 tethering.
      * @return null if there is no usable interface, or the interface name otherwise.
      */
-    public static @Nullable String getIPv6Interface(NetworkState ns) {
+    public static @Nullable String getIPv6Interface(UpstreamNetworkState ns) {
         // Broadly speaking:
         //
         //     [1] does the upstream have an IPv6 default route?
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 9769596..dc38c49a 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -32,7 +32,6 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
-import android.net.NetworkState;
 import android.net.util.PrefixUtils;
 import android.net.util.SharedLog;
 import android.os.Handler;
@@ -90,7 +89,7 @@
     private final StateMachine mTarget;
     private final Handler mHandler;
     private final int mWhat;
-    private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
+    private final HashMap<Network, UpstreamNetworkState> mNetworkMap = new HashMap<>();
     private HashSet<IpPrefix> mLocalPrefixes;
     private ConnectivityManager mCM;
     private EntitlementManager mEntitlementMgr;
@@ -236,7 +235,7 @@
     /**
      * Select the first available network from |perferredTypes|.
      */
-    public NetworkState selectPreferredUpstreamType(Iterable<Integer> preferredTypes) {
+    public UpstreamNetworkState selectPreferredUpstreamType(Iterable<Integer> preferredTypes) {
         final TypeStatePair typeStatePair = findFirstAvailableUpstreamByType(
                 mNetworkMap.values(), preferredTypes, isCellularUpstreamPermitted());
 
@@ -274,8 +273,8 @@
      * preferred upstream would be DUN otherwise preferred upstream is the same as default network.
      * Returns null if no current upstream is available.
      */
-    public NetworkState getCurrentPreferredUpstream() {
-        final NetworkState dfltState = (mDefaultInternetNetwork != null)
+    public UpstreamNetworkState getCurrentPreferredUpstream() {
+        final UpstreamNetworkState dfltState = (mDefaultInternetNetwork != null)
                 ? mNetworkMap.get(mDefaultInternetNetwork)
                 : null;
         if (isNetworkUsableAndNotCellular(dfltState)) return dfltState;
@@ -312,11 +311,11 @@
         if (mNetworkMap.containsKey(network)) return;
 
         if (VDBG) Log.d(TAG, "onAvailable for " + network);
-        mNetworkMap.put(network, new NetworkState(null, null, null, network, null, null));
+        mNetworkMap.put(network, new UpstreamNetworkState(null, null, network));
     }
 
     private void handleNetCap(Network network, NetworkCapabilities newNc) {
-        final NetworkState prev = mNetworkMap.get(network);
+        final UpstreamNetworkState prev = mNetworkMap.get(network);
         if (prev == null || newNc.equals(prev.networkCapabilities)) {
             // Ignore notifications about networks for which we have not yet
             // received onAvailable() (should never happen) and any duplicate
@@ -336,15 +335,15 @@
             mLog.logf("upstream network signal strength: %s -> %s", prevSignal, newSignal);
         }
 
-        mNetworkMap.put(network, new NetworkState(
-                null, prev.linkProperties, newNc, network, null, null));
+        mNetworkMap.put(network, new UpstreamNetworkState(
+                prev.linkProperties, newNc, network));
         // TODO: If sufficient information is available to select a more
         // preferable upstream, do so now and notify the target.
         notifyTarget(EVENT_ON_CAPABILITIES, network);
     }
 
     private void handleLinkProp(Network network, LinkProperties newLp) {
-        final NetworkState prev = mNetworkMap.get(network);
+        final UpstreamNetworkState prev = mNetworkMap.get(network);
         if (prev == null || newLp.equals(prev.linkProperties)) {
             // Ignore notifications about networks for which we have not yet
             // received onAvailable() (should never happen) and any duplicate
@@ -357,8 +356,8 @@
                     network, newLp));
         }
 
-        mNetworkMap.put(network, new NetworkState(
-                null, newLp, prev.networkCapabilities, network, null, null));
+        mNetworkMap.put(network, new UpstreamNetworkState(
+                newLp, prev.networkCapabilities, network));
         // TODO: If sufficient information is available to select a more
         // preferable upstream, do so now and notify the target.
         notifyTarget(EVENT_ON_LINKPROPERTIES, network);
@@ -509,11 +508,11 @@
 
     private static class TypeStatePair {
         public int type = TYPE_NONE;
-        public NetworkState ns = null;
+        public UpstreamNetworkState ns = null;
     }
 
     private static TypeStatePair findFirstAvailableUpstreamByType(
-            Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes,
+            Iterable<UpstreamNetworkState> netStates, Iterable<Integer> preferredTypes,
             boolean isCellularUpstreamPermitted) {
         final TypeStatePair result = new TypeStatePair();
 
@@ -532,7 +531,7 @@
 
             nc.setSingleUid(Process.myUid());
 
-            for (NetworkState value : netStates) {
+            for (UpstreamNetworkState value : netStates) {
                 if (!nc.satisfiedByNetworkCapabilities(value.networkCapabilities)) {
                     continue;
                 }
@@ -546,10 +545,10 @@
         return result;
     }
 
-    private static HashSet<IpPrefix> allLocalPrefixes(Iterable<NetworkState> netStates) {
+    private static HashSet<IpPrefix> allLocalPrefixes(Iterable<UpstreamNetworkState> netStates) {
         final HashSet<IpPrefix> prefixSet = new HashSet<>();
 
-        for (NetworkState ns : netStates) {
+        for (UpstreamNetworkState ns : netStates) {
             final LinkProperties lp = ns.linkProperties;
             if (lp == null) continue;
             prefixSet.addAll(PrefixUtils.localPrefixesFrom(lp));
@@ -563,7 +562,7 @@
         return Integer.toString(nc.getSignalStrength());
     }
 
-    private static boolean isCellular(NetworkState ns) {
+    private static boolean isCellular(UpstreamNetworkState ns) {
         return (ns != null) && isCellular(ns.networkCapabilities);
     }
 
@@ -572,18 +571,19 @@
                && nc.hasCapability(NET_CAPABILITY_NOT_VPN);
     }
 
-    private static boolean hasCapability(NetworkState ns, int netCap) {
+    private static boolean hasCapability(UpstreamNetworkState ns, int netCap) {
         return (ns != null) && (ns.networkCapabilities != null)
                && ns.networkCapabilities.hasCapability(netCap);
     }
 
-    private static boolean isNetworkUsableAndNotCellular(NetworkState ns) {
+    private static boolean isNetworkUsableAndNotCellular(UpstreamNetworkState ns) {
         return (ns != null) && (ns.networkCapabilities != null) && (ns.linkProperties != null)
                && !isCellular(ns.networkCapabilities);
     }
 
-    private static NetworkState findFirstDunNetwork(Iterable<NetworkState> netStates) {
-        for (NetworkState ns : netStates) {
+    private static UpstreamNetworkState findFirstDunNetwork(
+            Iterable<UpstreamNetworkState> netStates) {
+        for (UpstreamNetworkState ns : netStates) {
             if (isCellular(ns) && hasCapability(ns, NET_CAPABILITY_DUN)) return ns;
         }
 
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkState.java b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkState.java
new file mode 100644
index 0000000..68bb837
--- /dev/null
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkState.java
@@ -0,0 +1,51 @@
+/*
+ * 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.connectivity.tethering;
+
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Snapshot of tethering upstream network state.
+ */
+public class UpstreamNetworkState {
+    /** {@link LinkProperties}. */
+    public final LinkProperties linkProperties;
+    /** {@link NetworkCapabilities}. */
+    public final NetworkCapabilities networkCapabilities;
+    /** {@link Network}. */
+    public final Network network;
+
+    /** Constructs a new UpstreamNetworkState. */
+    public UpstreamNetworkState(LinkProperties linkProperties,
+            NetworkCapabilities networkCapabilities, Network network) {
+        this.linkProperties = linkProperties;
+        this.networkCapabilities = networkCapabilities;
+        this.network = network;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return String.format("UpstreamNetworkState{%s, %s, %s}",
+                network == null ? "null" : network,
+                networkCapabilities == null ? "null" : networkCapabilities,
+                linkProperties == null ? "null" : linkProperties);
+    }
+}
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 31ed823..9d9ad10 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
@@ -27,7 +27,6 @@
 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_MOBILE;
 import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
@@ -82,7 +81,6 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkRequest;
-import android.net.NetworkState;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
 import android.net.TetherStatesParcel;
@@ -360,10 +358,8 @@
         }
     }
 
-    private static NetworkState buildMobileUpstreamState(boolean withIPv4, boolean withIPv6,
-            boolean with464xlat) {
-        final NetworkInfo info = new NetworkInfo(TYPE_MOBILE, 0, null, null);
-        info.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
+    private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4,
+            boolean withIPv6, boolean with464xlat) {
         final LinkProperties prop = new LinkProperties();
         prop.setInterfaceName(TEST_MOBILE_IFNAME);
 
@@ -393,22 +389,22 @@
 
         final NetworkCapabilities capabilities = new NetworkCapabilities()
                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-        return new NetworkState(info, prop, capabilities, new Network(100), null, "netid");
+        return new UpstreamNetworkState(prop, capabilities, new Network(100));
     }
 
-    private static NetworkState buildMobileIPv4UpstreamState() {
+    private static UpstreamNetworkState buildMobileIPv4UpstreamState() {
         return buildMobileUpstreamState(true, false, false);
     }
 
-    private static NetworkState buildMobileIPv6UpstreamState() {
+    private static UpstreamNetworkState buildMobileIPv6UpstreamState() {
         return buildMobileUpstreamState(false, true, false);
     }
 
-    private static NetworkState buildMobileDualStackUpstreamState() {
+    private static UpstreamNetworkState buildMobileDualStackUpstreamState() {
         return buildMobileUpstreamState(true, true, false);
     }
 
-    private static NetworkState buildMobile464xlatUpstreamState() {
+    private static UpstreamNetworkState buildMobile464xlatUpstreamState() {
         return buildMobileUpstreamState(false, true, true);
     }
 
@@ -562,7 +558,7 @@
         verifyNoMoreInteractions(mWifiManager);
     }
 
-    private void prepareUsbTethering(NetworkState upstreamState) {
+    private void prepareUsbTethering(UpstreamNetworkState upstreamState) {
         when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState);
         when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any()))
                 .thenReturn(upstreamState);
@@ -577,7 +573,7 @@
 
     @Test
     public void testUsbConfiguredBroadcastStartsTethering() throws Exception {
-        NetworkState upstreamState = buildMobileIPv4UpstreamState();
+        UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
         prepareUsbTethering(upstreamState);
 
         // This should produce no activity of any kind.
@@ -657,14 +653,14 @@
     /**
      * Send CMD_IPV6_TETHER_UPDATE to IpServers as would be done by IPv6TetheringCoordinator.
      */
-    private void sendIPv6TetherUpdates(NetworkState upstreamState) {
+    private void sendIPv6TetherUpdates(UpstreamNetworkState upstreamState) {
         // IPv6TetheringCoordinator must have been notified of downstream
         verify(mIPv6TetheringCoordinator, times(1)).addActiveDownstream(
                 argThat(sm -> sm.linkProperties().getInterfaceName().equals(TEST_USB_IFNAME)),
                 eq(IpServer.STATE_TETHERED));
 
         for (IpServer ipSrv : mTetheringDependencies.mIpv6CoordinatorNotifyList) {
-            NetworkState ipv6OnlyState = buildMobileUpstreamState(false, true, false);
+            UpstreamNetworkState ipv6OnlyState = buildMobileUpstreamState(false, true, false);
             ipSrv.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, 0, 0,
                     upstreamState.linkProperties.isIpv6Provisioned()
                             ? ipv6OnlyState.linkProperties
@@ -673,7 +669,7 @@
         mLooper.dispatchAll();
     }
 
-    private void runUsbTethering(NetworkState upstreamState) {
+    private void runUsbTethering(UpstreamNetworkState upstreamState) {
         prepareUsbTethering(upstreamState);
         sendUsbBroadcast(true, true, true);
         mLooper.dispatchAll();
@@ -681,7 +677,7 @@
 
     @Test
     public void workingMobileUsbTethering_IPv4() throws Exception {
-        NetworkState upstreamState = buildMobileIPv4UpstreamState();
+        UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
         runUsbTethering(upstreamState);
 
         verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
@@ -696,7 +692,7 @@
     public void workingMobileUsbTethering_IPv4LegacyDhcp() {
         Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1);
         sendConfigurationChanged();
-        final NetworkState upstreamState = buildMobileIPv4UpstreamState();
+        final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
         runUsbTethering(upstreamState);
         sendIPv6TetherUpdates(upstreamState);
 
@@ -705,7 +701,7 @@
 
     @Test
     public void workingMobileUsbTethering_IPv6() throws Exception {
-        NetworkState upstreamState = buildMobileIPv6UpstreamState();
+        UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState();
         runUsbTethering(upstreamState);
 
         verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
@@ -718,7 +714,7 @@
 
     @Test
     public void workingMobileUsbTethering_DualStack() throws Exception {
-        NetworkState upstreamState = buildMobileDualStackUpstreamState();
+        UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
         runUsbTethering(upstreamState);
 
         verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
@@ -733,7 +729,7 @@
 
     @Test
     public void workingMobileUsbTethering_MultipleUpstreams() throws Exception {
-        NetworkState upstreamState = buildMobile464xlatUpstreamState();
+        UpstreamNetworkState upstreamState = buildMobile464xlatUpstreamState();
         runUsbTethering(upstreamState);
 
         verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
@@ -751,7 +747,7 @@
     @Test
     public void workingMobileUsbTethering_v6Then464xlat() throws Exception {
         // Setup IPv6
-        NetworkState upstreamState = buildMobileIPv6UpstreamState();
+        UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState();
         runUsbTethering(upstreamState);
 
         verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
@@ -789,7 +785,7 @@
         sendConfigurationChanged();
 
         // Setup IPv6
-        final NetworkState upstreamState = buildMobileIPv6UpstreamState();
+        final UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState();
         runUsbTethering(upstreamState);
 
         // UpstreamNetworkMonitor should choose upstream automatically
@@ -1172,7 +1168,7 @@
         TetherStatesParcel tetherState = callback.pollTetherStatesChanged();
         assertEquals(tetherState, null);
         // 2. Enable wifi tethering.
-        NetworkState upstreamState = buildMobileDualStackUpstreamState();
+        UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
         when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState);
         when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any()))
                 .thenReturn(upstreamState);
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 c028d6d..c90abbb 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
@@ -50,7 +50,6 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
-import android.net.NetworkState;
 import android.net.util.SharedLog;
 import android.os.Handler;
 import android.os.Message;
@@ -539,7 +538,7 @@
                 mUNM.selectPreferredUpstreamType(preferredTypes));
         verify(mEntitleMgr, times(1)).maybeRunProvisioning();
     }
-    private void assertSatisfiesLegacyType(int legacyType, NetworkState ns) {
+    private void assertSatisfiesLegacyType(int legacyType, UpstreamNetworkState ns) {
         if (legacyType == TYPE_NONE) {
             assertTrue(ns == null);
             return;
diff --git a/packages/VpnDialogs/res/values-am/strings.xml b/packages/VpnDialogs/res/values-am/strings.xml
index 103f101..ad9773b 100644
--- a/packages/VpnDialogs/res/values-am/strings.xml
+++ b/packages/VpnDialogs/res/values-am/strings.xml
@@ -30,7 +30,7 @@
     <string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
     <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"የቪፒኤን ቅንብሮችን ይቀይሩ"</string>
     <string name="configure" msgid="4905518375574791375">"አዋቅር"</string>
-    <string name="disconnect" msgid="971412338304200056">"አለያይ"</string>
+    <string name="disconnect" msgid="971412338304200056">"ግንኙነት አቋርጥ"</string>
     <string name="open_app" msgid="3717639178595958667">"መተግበሪያን ክፈት"</string>
     <string name="dismiss" msgid="6192859333764711227">"አሰናብት"</string>
 </resources>
diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk
index eecc101..8a3ac94 100644
--- a/packages/overlays/Android.mk
+++ b/packages/overlays/Android.mk
@@ -51,7 +51,8 @@
 	NavigationBarModeGesturalOverlay \
 	NavigationBarModeGesturalOverlayNarrowBack \
 	NavigationBarModeGesturalOverlayWideBack \
-	NavigationBarModeGesturalOverlayExtraWideBack
+	NavigationBarModeGesturalOverlayExtraWideBack \
+	preinstalled-packages-platform-overlays.xml
 
 include $(BUILD_PHONY_PACKAGE)
 include $(CLEAR_VARS)
diff --git a/services/Android.bp b/services/Android.bp
index 501496d..ede4c3e 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -119,6 +119,12 @@
         " --hide DeprecationMismatch" +
         " --hide HiddenTypedefConstant",
     visibility: ["//visibility:private"],
+    check_api: {
+        current: {
+            api_file: "api/current.txt",
+            removed_api_file: "api/removed.txt",
+        },
+    },
 }
 
 java_library {
diff --git a/services/api/current.txt b/services/api/current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/services/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/services/api/removed.txt b/services/api/removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/services/api/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java b/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java
index 9db6254..36a4509 100644
--- a/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java
+++ b/services/autofill/java/com/android/server/autofill/FieldClassificationStrategy.java
@@ -121,7 +121,12 @@
         synchronized (mLock) {
             if (mServiceConnection != null) {
                 if (sDebug) Slog.d(TAG, "reset(): unbinding service.");
-                mContext.unbindService(mServiceConnection);
+                try {
+                    mContext.unbindService(mServiceConnection);
+                } catch (IllegalArgumentException e) {
+                    // no-op, just log the error message.
+                    Slog.w(TAG, "reset(): " + e.getMessage());
+                }
                 mServiceConnection = null;
             } else {
                 if (sDebug) Slog.d(TAG, "reset(): service is not bound. Do nothing.");
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 46ff718..c741fce 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1592,7 +1592,7 @@
                                 TelephonyManager.DATA_UNKNOWN,
                                 TelephonyManager.NETWORK_TYPE_UNKNOWN,
                                 ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
-                                DataFailCause.NONE));
+                                DataFailCause.NONE, null));
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
                             PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
@@ -1609,7 +1609,6 @@
 
             handleRemoveListLocked();
         }
-        broadcastDataConnectionFailed(apnType, subId);
     }
 
     @Override
@@ -1778,7 +1777,7 @@
                                 TelephonyManager.DATA_UNKNOWN,
                                 TelephonyManager.NETWORK_TYPE_UNKNOWN,
                                 ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
-                                failCause));
+                                failCause, null));
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
                             PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
@@ -2260,13 +2259,6 @@
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
-    private void broadcastDataConnectionFailed(String apnType, int subId) {
-        Intent intent = new Intent(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
-        intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType);
-        intent.putExtra(PHONE_CONSTANTS_SUBSCRIPTION_KEY, subId);
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
     private void enforceNotifyPermissionOrCarrierPrivilege(String method) {
         if (checkNotifyPermission()) {
             return;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index dab928a..154d16f 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2960,7 +2960,9 @@
                     } catch (Exception e) {
                         Slog.w(TAG, "Exception when unbinding service "
                                 + r.shortInstanceName, e);
+                        needOomAdj = false;
                         serviceProcessGoneLocked(r);
+                        break;
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 772f9b3..a1e1f29 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -282,20 +282,27 @@
             "persist.device_config.runtime_native.use_app_image_startup_cache";
 
     // Low Memory Killer Daemon command codes.
-    // These must be kept in sync with the definitions in lmkd.c
+    // These must be kept in sync with lmk_cmd definitions in lmkd.h
     //
     // LMK_TARGET <minfree> <minkillprio> ... (up to 6 pairs)
     // LMK_PROCPRIO <pid> <uid> <prio>
     // LMK_PROCREMOVE <pid>
     // LMK_PROCPURGE
     // LMK_GETKILLCNT
+    // LMK_SUBSCRIBE
     // LMK_PROCKILL
     static final byte LMK_TARGET = 0;
     static final byte LMK_PROCPRIO = 1;
     static final byte LMK_PROCREMOVE = 2;
     static final byte LMK_PROCPURGE = 3;
     static final byte LMK_GETKILLCNT = 4;
-    static final byte LMK_PROCKILL = 5; // Note: this is an unsolicated command
+    static final byte LMK_SUBSCRIBE = 5;
+    static final byte LMK_PROCKILL = 6; // Note: this is an unsolicated command
+
+    // Low Memory Killer Daemon command codes.
+    // These must be kept in sync with async_event_type definitions in lmkd.h
+    //
+    static final int LMK_ASYNC_EVENT_KILL = 0;
 
     // lmkd reconnect delay in msecs
     private static final long LMKD_RECONNECT_DELAY_MS = 1000;
@@ -1354,6 +1361,11 @@
                 }
                 ostream.write(buf.array(), 0, buf.position());
             }
+            // Subscribe for kill event notifications
+            buf = ByteBuffer.allocate(4 * 2);
+            buf.putInt(LMK_SUBSCRIBE);
+            buf.putInt(LMK_ASYNC_EVENT_KILL);
+            ostream.write(buf.array(), 0, buf.position());
         } catch (IOException ex) {
             return false;
         }
@@ -2089,6 +2101,10 @@
      */
     @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) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 00fc6d0..75d9dd8 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -77,6 +77,9 @@
     // List of preferred devices for strategies
     private final ArrayMap<Integer, AudioDeviceAddress> mPreferredDevices = new ArrayMap<>();
 
+    // the wrapper for AudioSystem static methods, allows us to spy AudioSystem
+    private final @NonNull AudioSystemAdapter mAudioSystem;
+
     private @NonNull AudioDeviceBroker mDeviceBroker;
 
     // Monitoring of audio routes.  Protected by mAudioRoutes.
@@ -86,12 +89,14 @@
 
     /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
         mDeviceBroker = broker;
+        mAudioSystem = AudioSystemAdapter.getDefaultAdapter();
     }
 
     //-----------------------------------------------------------
-    /** for mocking only */
-    /*package*/ AudioDeviceInventory() {
+    /** for mocking only, allows to inject AudioSystem adapter */
+    /*package*/ AudioDeviceInventory(@NonNull AudioSystemAdapter audioSystem) {
         mDeviceBroker = null;
+        mAudioSystem = audioSystem;
     }
 
     /*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) {
@@ -185,7 +190,7 @@
         synchronized (mDevicesLock) {
             //TODO iterate on mApmConnectedDevices instead once it handles all device types
             for (DeviceInfo di : mConnectedDevices.values()) {
-                AudioSystem.setDeviceConnectionState(
+                mAudioSystem.setDeviceConnectionState(
                         di.mDeviceType,
                         AudioSystem.DEVICE_STATE_AVAILABLE,
                         di.mDeviceAddress,
@@ -195,7 +200,7 @@
         }
         synchronized (mPreferredDevices) {
             mPreferredDevices.forEach((strategy, device) -> {
-                AudioSystem.setPreferredDeviceForStrategy(strategy, device); });
+                mAudioSystem.setPreferredDeviceForStrategy(strategy, device); });
         }
     }
 
@@ -355,7 +360,7 @@
                     mConnectedDevices.replace(key, di);
                 }
             }
-            final int res = AudioSystem.handleDeviceConfigChange(
+            final int res = mAudioSystem.handleDeviceConfigChange(
                     AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
                     BtHelper.getName(btDevice), a2dpCodec);
 
@@ -477,7 +482,7 @@
     /*package*/ int setPreferredDeviceForStrategySync(int strategy,
                                                       @NonNull AudioDeviceAddress device) {
         final long identity = Binder.clearCallingIdentity();
-        final int status = AudioSystem.setPreferredDeviceForStrategy(strategy, device);
+        final int status = mAudioSystem.setPreferredDeviceForStrategy(strategy, device);
         Binder.restoreCallingIdentity(identity);
 
         if (status == AudioSystem.SUCCESS) {
@@ -488,7 +493,7 @@
 
     /*package*/ int removePreferredDeviceForStrategySync(int strategy) {
         final long identity = Binder.clearCallingIdentity();
-        final int status = AudioSystem.removePreferredDeviceForStrategy(strategy);
+        final int status = mAudioSystem.removePreferredDeviceForStrategy(strategy);
         Binder.restoreCallingIdentity(identity);
 
         if (status == AudioSystem.SUCCESS) {
@@ -523,7 +528,7 @@
                 Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected);
             }
             if (connect && !isConnected) {
-                final int res = AudioSystem.setDeviceConnectionState(device,
+                final int res = mAudioSystem.setDeviceConnectionState(device,
                         AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
                         AudioSystem.AUDIO_FORMAT_DEFAULT);
                 if (res != AudioSystem.AUDIO_STATUS_OK) {
@@ -536,7 +541,7 @@
                 mDeviceBroker.postAccessoryPlugMediaUnmute(device);
                 return true;
             } else if (!connect && isConnected) {
-                AudioSystem.setDeviceConnectionState(device,
+                mAudioSystem.setDeviceConnectionState(device,
                         AudioSystem.DEVICE_STATE_UNAVAILABLE, address, deviceName,
                         AudioSystem.AUDIO_FORMAT_DEFAULT);
                 // always remove even if disconnection failed
@@ -713,7 +718,7 @@
         mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource);
         // at this point there could be another A2DP device already connected in APM, but it
         // doesn't matter as this new one will overwrite the previous one
-        final int res = AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+        final int res = mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
                 AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
 
         if (res != AudioSystem.AUDIO_STATUS_OK) {
@@ -728,7 +733,7 @@
         }
 
         // Reset A2DP suspend state each time a new sink is connected
-        AudioSystem.setParameters("A2dpSuspended=false");
+        mAudioSystem.setParameters("A2dpSuspended=false");
 
         final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
                 address, a2dpCodec);
@@ -761,7 +766,7 @@
 
         // device to remove was visible by APM, update APM
         mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false);
-        final int res = AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+        final int res = mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
                 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
 
         if (res != AudioSystem.AUDIO_STATUS_OK) {
@@ -783,7 +788,7 @@
     private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
         // prevent any activity on the A2DP audio output to avoid unwanted
         // reconnection of the sink.
-        AudioSystem.setParameters("A2dpSuspended=true");
+        mAudioSystem.setParameters("A2dpSuspended=true");
         // retrieve DeviceInfo before removing device
         final String deviceKey =
                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
@@ -799,7 +804,7 @@
 
     @GuardedBy("mDevicesLock")
     private void makeA2dpSrcAvailable(String address) {
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
+        mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
                 AudioSystem.DEVICE_STATE_AVAILABLE, address, "",
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.put(
@@ -810,7 +815,7 @@
 
     @GuardedBy("mDevicesLock")
     private void makeA2dpSrcUnavailable(String address) {
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
+        mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP,
                 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.remove(
@@ -824,7 +829,7 @@
                 AudioSystem.DEVICE_OUT_HEARING_AID);
         mDeviceBroker.postSetHearingAidVolumeIndex(hearingAidVolIndex, streamType);
 
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
+        mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
                 AudioSystem.DEVICE_STATE_AVAILABLE, address, name,
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.put(
@@ -839,7 +844,7 @@
 
     @GuardedBy("mDevicesLock")
     private void makeHearingAidDeviceUnavailable(String address) {
-        AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
+        mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_HEARING_AID,
                 AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "",
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.remove(
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 21cecc2..114aac9 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -415,8 +415,10 @@
         public void onError(int error) {
             switch (error) {
                 case AudioSystem.AUDIO_STATUS_SERVER_DIED:
-                    mRecordMonitor.onAudioServerDied();
-
+                    // check for null in case error callback is called during instance creation
+                    if (mRecordMonitor != null) {
+                        mRecordMonitor.onAudioServerDied();
+                    }
                     sendMsg(mAudioHandler, MSG_AUDIO_SERVER_DIED,
                             SENDMSG_NOOP, 0, 0, null, 0);
                     sendMsg(mAudioHandler, MSG_DISPATCH_AUDIO_SERVER_STATE,
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
new file mode 100644
index 0000000..9d06b42
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -0,0 +1,155 @@
+/*
+ * 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.audio;
+
+import android.annotation.NonNull;
+import android.media.AudioDeviceAddress;
+import android.media.AudioSystem;
+import android.util.Log;
+
+/**
+ * Provides an adapter to access functionality of the android.media.AudioSystem class for device
+ * related functionality.
+ * Use the "real" AudioSystem through the default adapter.
+ * Use the "always ok" adapter to avoid dealing with the APM behaviors during a test.
+ */
+public class AudioSystemAdapter {
+
+    /**
+     * Create a wrapper around the {@link AudioSystem} static methods, all functions are directly
+     * forwarded to the AudioSystem class.
+     * @return an adapter around AudioSystem
+     */
+    static final @NonNull AudioSystemAdapter getDefaultAdapter() {
+        return new AudioSystemAdapter();
+    }
+
+    /**
+     * Create an adapter for AudioSystem that always succeeds, and does nothing.
+     * @return a no-op AudioSystem adapter
+     */
+    static final @NonNull AudioSystemAdapter getAlwaysOkAdapter() {
+        return new AudioSystemOkAdapter();
+    }
+
+    /**
+     * Same as {@link AudioSystem#setDeviceConnectionState(int, int, String, String, int)}
+     * @param device
+     * @param state
+     * @param deviceAddress
+     * @param deviceName
+     * @param codecFormat
+     * @return
+     */
+    public int setDeviceConnectionState(int device, int state, String deviceAddress,
+                                        String deviceName, int codecFormat) {
+        return AudioSystem.setDeviceConnectionState(device, state, deviceAddress, deviceName,
+                codecFormat);
+    }
+
+    /**
+     * Same as {@link AudioSystem#getDeviceConnectionState(int, String)}
+     * @param device
+     * @param deviceAddress
+     * @return
+     */
+    public int getDeviceConnectionState(int device, String deviceAddress) {
+        return AudioSystem.getDeviceConnectionState(device, deviceAddress);
+    }
+
+    /**
+     * Same as {@link AudioSystem#handleDeviceConfigChange(int, String, String, int)}
+     * @param device
+     * @param deviceAddress
+     * @param deviceName
+     * @param codecFormat
+     * @return
+     */
+    public int handleDeviceConfigChange(int device, String deviceAddress,
+                                               String deviceName, int codecFormat) {
+        return AudioSystem.handleDeviceConfigChange(device, deviceAddress, deviceName,
+                codecFormat);
+    }
+
+    /**
+     * Same as {@link AudioSystem#setPreferredDeviceForStrategy(int, AudioDeviceAddress)}
+     * @param strategy
+     * @param device
+     * @return
+     */
+    public int setPreferredDeviceForStrategy(int strategy, @NonNull AudioDeviceAddress device) {
+        return AudioSystem.setPreferredDeviceForStrategy(strategy, device);
+    }
+
+    /**
+     * Same as {@link AudioSystem#removePreferredDeviceForStrategy(int)}
+     * @param strategy
+     * @return
+     */
+    public int removePreferredDeviceForStrategy(int strategy) {
+        return AudioSystem.removePreferredDeviceForStrategy(strategy);
+    }
+
+    /**
+     * Same as {@link AudioSystem#setParameters(String)}
+     * @param keyValuePairs
+     * @return
+     */
+    public int setParameters(String keyValuePairs) {
+        return AudioSystem.setParameters(keyValuePairs);
+    }
+
+    //--------------------------------------------------------------------
+    protected static class AudioSystemOkAdapter extends AudioSystemAdapter {
+        private static final String TAG = "ASA";
+
+        @Override
+        public int setDeviceConnectionState(int device, int state, String deviceAddress,
+                                            String deviceName, int codecFormat) {
+            Log.i(TAG, String.format("setDeviceConnectionState(0x%s, %s, %s, 0x%s",
+                    Integer.toHexString(device), state, deviceAddress, deviceName,
+                    Integer.toHexString(codecFormat)));
+            return AudioSystem.AUDIO_STATUS_OK;
+        }
+
+        @Override
+        public int getDeviceConnectionState(int device, String deviceAddress) {
+            return AudioSystem.AUDIO_STATUS_OK;
+        }
+
+        @Override
+        public int handleDeviceConfigChange(int device, String deviceAddress,
+                                                   String deviceName, int codecFormat) {
+            return AudioSystem.AUDIO_STATUS_OK;
+        }
+
+        @Override
+        public int setPreferredDeviceForStrategy(int strategy, @NonNull AudioDeviceAddress device) {
+            return AudioSystem.AUDIO_STATUS_OK;
+        }
+
+        @Override
+        public int removePreferredDeviceForStrategy(int strategy) {
+            return AudioSystem.AUDIO_STATUS_OK;
+        }
+
+        @Override
+        public int setParameters(String keyValuePairs) {
+            return AudioSystem.AUDIO_STATUS_OK;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index 2321afb..5250a77 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -337,7 +337,6 @@
                               .collect(Collectors.toList()))
                 : useTls ? paramsParcel.servers  // Opportunistic
                 : new String[0];            // Off
-        paramsParcel.tlsFingerprints = new String[0];
         // Prepare to track the validation status of the DNS servers in the
         // resolver config when private DNS is in opportunistic or strict mode.
         if (useTls) {
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index d57ce53..e69a3b8b 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -152,6 +152,24 @@
     public void setRequestedColorModeLocked(int colorMode) {
     }
 
+    /**
+     * Sends the Auto Low Latency Mode (ALLM) signal over HDMI, or requests an internal display to
+     * switch to a low-latency mode.
+     *
+     * @param on Whether to set ALLM on or off.
+     */
+    public void setAutoLowLatencyModeLocked(boolean on) {
+    }
+
+    /**
+     * Sends a ContentType=Game signal over HDMI, or requests an internal display to switch to a
+     * game mode (generally lower latency).
+     *
+     * @param on Whether to send a ContentType=Game signal or not
+     */
+    public void setGameContentTypeLocked(boolean on) {
+    }
+
     public void onOverlayChangedLocked() {
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 729ea17..ac41434 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -207,6 +207,16 @@
     public Display.HdrCapabilities hdrCapabilities;
 
     /**
+     * Indicates whether this display supports Auto Low Latency Mode.
+     */
+    public boolean allmSupported;
+
+    /**
+     * Indicates whether this display suppors Game content type.
+     */
+    public boolean gameContentTypeSupported;
+
+    /**
      * The nominal apparent density of the display in DPI used for layout calculations.
      * This density is sensitive to the viewing distance.  A big TV and a tablet may have
      * the same apparent density even though the pixels on the TV are much bigger than
@@ -337,6 +347,8 @@
                 || !Arrays.equals(supportedModes, other.supportedModes)
                 || !Arrays.equals(supportedColorModes, other.supportedColorModes)
                 || !Objects.equals(hdrCapabilities, other.hdrCapabilities)
+                || allmSupported != other.allmSupported
+                || gameContentTypeSupported != other.gameContentTypeSupported
                 || densityDpi != other.densityDpi
                 || xDpi != other.xDpi
                 || yDpi != other.yDpi
@@ -371,6 +383,8 @@
         colorMode = other.colorMode;
         supportedColorModes = other.supportedColorModes;
         hdrCapabilities = other.hdrCapabilities;
+        allmSupported = other.allmSupported;
+        gameContentTypeSupported = other.gameContentTypeSupported;
         densityDpi = other.densityDpi;
         xDpi = other.xDpi;
         yDpi = other.yDpi;
@@ -400,6 +414,8 @@
         sb.append(", colorMode ").append(colorMode);
         sb.append(", supportedColorModes ").append(Arrays.toString(supportedColorModes));
         sb.append(", HdrCapabilities ").append(hdrCapabilities);
+        sb.append(", allmSupported ").append(allmSupported);
+        sb.append(", gameContentTypeSupported ").append(gameContentTypeSupported);
         sb.append(", density ").append(densityDpi);
         sb.append(", ").append(xDpi).append(" x ").append(yDpi).append(" dpi");
         sb.append(", appVsyncOff ").append(appVsyncOffsetNanos);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ea03131..10386e7 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1193,12 +1193,16 @@
     }
 
     private void setDisplayPropertiesInternal(int displayId, boolean hasContent,
-            float requestedRefreshRate, int requestedModeId, boolean inTraversal) {
+            float requestedRefreshRate, int requestedModeId, boolean requestedMinimalPostProcessing,
+            boolean inTraversal) {
         synchronized (mSyncRoot) {
             LogicalDisplay display = mLogicalDisplays.get(displayId);
             if (display == null) {
                 return;
             }
+
+            boolean shouldScheduleTraversal = false;
+
             if (display.hasContentLocked() != hasContent) {
                 if (DEBUG) {
                     Slog.d(TAG, "Display " + displayId + " hasContent flag changed: "
@@ -1206,7 +1210,7 @@
                 }
 
                 display.setHasContentLocked(hasContent);
-                scheduleTraversalLocked(inTraversal);
+                shouldScheduleTraversal = true;
             }
             if (requestedModeId == 0 && requestedRefreshRate != 0) {
                 // Scan supported modes returned by display.getInfo() to find a mode with the same
@@ -1216,6 +1220,20 @@
             }
             mDisplayModeDirector.getAppRequestObserver().setAppRequestedMode(
                     displayId, requestedModeId);
+
+
+            if (display.getDisplayInfoLocked().minimalPostProcessingSupported
+                    && (display.getRequestedMinimalPostProcessingLocked()
+                    != requestedMinimalPostProcessing)) {
+
+                display.setRequestedMinimalPostProcessingLocked(requestedMinimalPostProcessing);
+
+                shouldScheduleTraversal = true;
+            }
+
+            if (shouldScheduleTraversal) {
+                scheduleTraversalLocked(inTraversal);
+            }
         }
     }
 
@@ -2349,6 +2367,7 @@
     }
 
     private final class LocalService extends DisplayManagerInternal {
+
         @Override
         public void initPowerManagement(final DisplayPowerCallbacks callbacks, Handler handler,
                 SensorManager sensorManager) {
@@ -2437,9 +2456,10 @@
 
         @Override
         public void setDisplayProperties(int displayId, boolean hasContent,
-                float requestedRefreshRate, int requestedMode, boolean inTraversal) {
+                float requestedRefreshRate, int requestedMode,
+                boolean requestedMinimalPostProcessing, boolean inTraversal) {
             setDisplayPropertiesInternal(displayId, hasContent, requestedRefreshRate,
-                    requestedMode, inTraversal);
+                    requestedMode, requestedMinimalPostProcessing, inTraversal);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index bf58efe..eebc738 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -180,6 +180,10 @@
         private int mActiveColorMode;
         private boolean mActiveColorModeInvalid;
         private Display.HdrCapabilities mHdrCapabilities;
+        private boolean mAllmSupported;
+        private boolean mGameContentTypeSupported;
+        private boolean mAllmRequested;
+        private boolean mGameContentTypeRequested;
         private boolean mSidekickActive;
         private SidekickInternal mSidekickInternal;
 
@@ -203,6 +207,8 @@
                 mBacklight = null;
             }
             mHdrCapabilities = SurfaceControl.getHdrCapabilities(displayToken);
+            mAllmSupported = SurfaceControl.getAutoLowLatencyModeSupport(displayToken);
+            mGameContentTypeSupported = SurfaceControl.getGameContentTypeSupport(displayToken);
         }
 
         @Override
@@ -412,6 +418,8 @@
                 mInfo.defaultModeId = mDefaultModeId;
                 mInfo.supportedModes = getDisplayModes(mSupportedModes);
                 mInfo.colorMode = mActiveColorMode;
+                mInfo.allmSupported = mAllmSupported;
+                mInfo.gameContentTypeSupported = mGameContentTypeSupported;
                 mInfo.supportedColorModes =
                         new int[mSupportedColorModes.size()];
                 for (int i = 0; i < mSupportedColorModes.size(); i++) {
@@ -716,6 +724,40 @@
         }
 
         @Override
+        public void setAutoLowLatencyModeLocked(boolean on) {
+            if (mAllmRequested == on) {
+                return;
+            }
+
+            mAllmRequested = on;
+
+            if (!mAllmSupported) {
+                Slog.d(TAG, "Unable to set ALLM because the connected display "
+                        + "does not support ALLM.");
+                return;
+            }
+
+            SurfaceControl.setAutoLowLatencyMode(getDisplayTokenLocked(), on);
+        }
+
+        @Override
+        public void setGameContentTypeLocked(boolean on) {
+            if (mGameContentTypeRequested == on) {
+                return;
+            }
+
+            mGameContentTypeRequested = on;
+
+            if (!mGameContentTypeSupported) {
+                Slog.d(TAG, "Unable to set game content type because the connected "
+                        + "display does not support game content type.");
+                return;
+            }
+
+            SurfaceControl.setGameContentType(getDisplayTokenLocked(), on);
+        }
+
+        @Override
         public void dumpLocked(PrintWriter pw) {
             super.dumpLocked(pw);
             pw.println("mPhysicalDisplayId=" + mPhysicalDisplayId);
@@ -728,6 +770,10 @@
             pw.println("mState=" + Display.stateToString(mState));
             pw.println("mBrightness=" + mBrightness);
             pw.println("mBacklight=" + mBacklight);
+            pw.println("mAllmSupported=" + mAllmSupported);
+            pw.println("mAllmRequested=" + mAllmRequested);
+            pw.println("mGameContentTypeSupported" + mGameContentTypeSupported);
+            pw.println("mGameContentTypeRequested" + mGameContentTypeRequested);
             pw.println("mDisplayInfos=");
             for (int i = 0; i < mDisplayInfos.length; i++) {
                 pw.println("  " + mDisplayInfos[i]);
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index b649a50..0c9445a 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -88,6 +88,7 @@
     private boolean mHasContent;
 
     private int mRequestedColorMode;
+    private boolean mRequestedMinimalPostProcessing;
 
     private DisplayModeDirector.DesiredDisplayModeSpecs mDesiredDisplayModeSpecs =
             new DisplayModeDirector.DesiredDisplayModeSpecs();
@@ -284,6 +285,8 @@
                     deviceInfo.supportedColorModes,
                     deviceInfo.supportedColorModes.length);
             mBaseDisplayInfo.hdrCapabilities = deviceInfo.hdrCapabilities;
+            mBaseDisplayInfo.minimalPostProcessingSupported =
+                    deviceInfo.allmSupported || deviceInfo.gameContentTypeSupported;
             mBaseDisplayInfo.logicalDensityDpi = deviceInfo.densityDpi;
             mBaseDisplayInfo.physicalXDpi = deviceInfo.xDpi;
             mBaseDisplayInfo.physicalYDpi = deviceInfo.yDpi;
@@ -363,6 +366,9 @@
             device.setRequestedColorModeLocked(0);
         }
 
+        device.setAutoLowLatencyModeLocked(mRequestedMinimalPostProcessing);
+        device.setGameContentTypeLocked(mRequestedMinimalPostProcessing);
+
         // Only grab the display info now as it may have been changed based on the requests above.
         final DisplayInfo displayInfo = getDisplayInfoLocked();
         final DisplayDeviceInfo displayDeviceInfo = device.getDisplayDeviceInfoLocked();
@@ -485,6 +491,23 @@
         mRequestedColorMode = colorMode;
     }
 
+    /**
+     * Returns the last requested minimal post processing setting.
+     */
+    public boolean getRequestedMinimalPostProcessingLocked() {
+        return mRequestedMinimalPostProcessing;
+    }
+
+    /**
+     * Instructs the connected display to do minimal post processing. This is implemented either
+     * via HDMI 2.1 ALLM or HDMI 1.4 ContentType=Game.
+     *
+     * @param on Whether to set minimal post processing on/off on the connected display.
+     */
+    public void setRequestedMinimalPostProcessingLocked(boolean on) {
+        mRequestedMinimalPostProcessing = on;
+    }
+
     /** Returns the pending requested color mode. */
     public int getRequestedColorModeLocked() {
         return mRequestedColorMode;
@@ -542,5 +565,6 @@
                 mPrimaryDisplayDevice.getNameLocked() : "null"));
         pw.println("mBaseDisplayInfo=" + mBaseDisplayInfo);
         pw.println("mOverrideDisplayInfo=" + mOverrideDisplayInfo);
+        pw.println("mRequestedMinimalPostProcessing=" + mRequestedMinimalPostProcessing);
     }
 }
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerService.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerService.java
index 005fb69..3762ebb 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerService.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerService.java
@@ -37,7 +37,7 @@
 
     @Override
     public void onStart() {
-        mService = new AppIntegrityManagerServiceImpl(mContext);
-        // TODO: define and publish a binder service.
+        mService = AppIntegrityManagerServiceImpl.create(mContext);
+        publishBinderService(Context.APP_INTEGRITY_SERVICE, mService);
     }
 }
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index 5c4479a..e5292a0 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -17,38 +17,107 @@
 package com.android.server.integrity;
 
 import static android.content.Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION;
+import static android.content.Intent.EXTRA_ORIGINATING_UID;
+import static android.content.Intent.EXTRA_PACKAGE_NAME;
+import static android.content.Intent.EXTRA_VERSION_CODE;
+import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
+import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
+import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
 import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
 
+import static com.android.server.integrity.IntegrityUtils.getHexDigest;
+
+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.content.IntentSender;
+import android.content.integrity.AppInstallMetadata;
+import android.content.integrity.IAppIntegrityManager;
+import android.content.integrity.Rule;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.Signature;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
+import com.android.server.integrity.engine.RuleEvaluationEngine;
+import com.android.server.integrity.model.IntegrityCheckResult;
+import com.android.server.integrity.model.RuleMetadata;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Map;
 
 /** Implementation of {@link AppIntegrityManagerService}. */
-class AppIntegrityManagerServiceImpl {
+public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
     private static final String TAG = "AppIntegrityManagerServiceImpl";
 
     private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
+    private static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
+    private static final String PACKAGE_INSTALLER = "com.google.android.packageinstaller";
+    private static final String BASE_APK_FILE = "base.apk";
+    private static final String ALLOWED_INSTALLERS_METADATA_NAME = "allowed-installers";
+    private static final String ALLOWED_INSTALLER_DELIMITER = ",";
+    private static final String INSTALLER_PACKAGE_CERT_DELIMITER = "\\|";
 
+    private static final String ADB_INSTALLER = "adb";
+    private static final String UNKNOWN_INSTALLER = "";
+    private static final String INSTALLER_CERT_NOT_APPLICABLE = "";
+
+    // Access to files inside mRulesDir is protected by mRulesLock;
     private final Context mContext;
     private final Handler mHandler;
     private final PackageManagerInternal mPackageManagerInternal;
+    private final RuleEvaluationEngine mEvaluationEngine;
+    private final IntegrityFileManager mIntegrityFileManager;
 
-    AppIntegrityManagerServiceImpl(Context context) {
-        mContext = context;
-
+    /** Create an instance of {@link AppIntegrityManagerServiceImpl}. */
+    public static AppIntegrityManagerServiceImpl create(Context context) {
         HandlerThread handlerThread = new HandlerThread("AppIntegrityManagerServiceHandler");
         handlerThread.start();
-        mHandler = handlerThread.getThreadHandler();
 
-        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+        return new AppIntegrityManagerServiceImpl(
+                context,
+                LocalServices.getService(PackageManagerInternal.class),
+                RuleEvaluationEngine.getRuleEvaluationEngine(),
+                IntegrityFileManager.getInstance(),
+                handlerThread.getThreadHandler());
+    }
+
+    @VisibleForTesting
+    AppIntegrityManagerServiceImpl(
+            Context context,
+            PackageManagerInternal packageManagerInternal,
+            RuleEvaluationEngine evaluationEngine,
+            IntegrityFileManager integrityFileManager,
+            Handler handler) {
+        mContext = context;
+        mPackageManagerInternal = packageManagerInternal;
+        mEvaluationEngine = evaluationEngine;
+        mIntegrityFileManager = integrityFileManager;
+        mHandler = handler;
 
         IntentFilter integrityVerificationFilter = new IntentFilter();
         integrityVerificationFilter.addAction(ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION);
@@ -74,14 +143,409 @@
                 mHandler);
     }
 
-    // protected broadcasts cannot be sent in the test.
-    @VisibleForTesting
-    void handleIntegrityVerification(Intent intent) {
+    @Override
+    public void updateRuleSet(
+            String version, ParceledListSlice<Rule> rules, IntentSender statusReceiver)
+            throws RemoteException {
+        String ruleProvider = getCallerPackageNameOrThrow();
+
+        mHandler.post(
+                () -> {
+                    boolean success = true;
+                    try {
+                        mIntegrityFileManager.writeRules(version, ruleProvider, rules.getList());
+                    } catch (Exception e) {
+                        Slog.e(TAG, "Error writing rules.", e);
+                        success = false;
+                    }
+
+                    Intent intent = new Intent();
+                    intent.putExtra(EXTRA_STATUS, success ? STATUS_SUCCESS : STATUS_FAILURE);
+                    try {
+                        statusReceiver.sendIntent(
+                                mContext,
+                                /* code= */ 0,
+                                intent,
+                                /* onFinished= */ null,
+                                /* handler= */ null);
+                    } catch (IntentSender.SendIntentException e) {
+                        Slog.e(TAG, "Error sending status feedback.", e);
+                    }
+                });
+    }
+
+    @Override
+    public String getCurrentRuleSetVersion() throws RemoteException {
+        getCallerPackageNameOrThrow();
+
+        RuleMetadata ruleMetadata = mIntegrityFileManager.readMetadata();
+        return (ruleMetadata != null && ruleMetadata.getVersion() != null)
+                ? ruleMetadata.getVersion()
+                : "";
+    }
+
+    @Override
+    public String getCurrentRuleSetProvider() throws RemoteException {
+        getCallerPackageNameOrThrow();
+
+        RuleMetadata ruleMetadata = mIntegrityFileManager.readMetadata();
+        return (ruleMetadata != null && ruleMetadata.getRuleProvider() != null)
+                ? ruleMetadata.getRuleProvider()
+                : "";
+    }
+
+    private void handleIntegrityVerification(Intent intent) {
         int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
-        // TODO: implement this method.
-        Slog.i(TAG, "Received integrity verification intent " + intent.toString());
-        Slog.i(TAG, "Extras " + intent.getExtras());
-        mPackageManagerInternal.setIntegrityVerificationResult(
-                verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
+        try {
+            Slog.i(TAG, "Received integrity verification intent " + intent.toString());
+            Slog.i(TAG, "Extras " + intent.getExtras());
+
+            String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
+
+            PackageInfo packageInfo = getPackageArchiveInfo(intent.getData());
+            if (packageInfo == null) {
+                Slog.w(TAG, "Cannot parse package " + packageName);
+                // We can't parse the package.
+                mPackageManagerInternal.setIntegrityVerificationResult(
+                        verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
+                return;
+            }
+
+            String installerPackageName = getInstallerPackageName(intent);
+            String appCert = getCertificateFingerprint(packageInfo);
+
+            AppInstallMetadata.Builder builder = new AppInstallMetadata.Builder();
+
+            builder.setPackageName(getPackageNameNormalized(packageName));
+            builder.setAppCertificate(appCert == null ? "" : appCert);
+            builder.setVersionCode(intent.getIntExtra(EXTRA_VERSION_CODE, -1));
+            builder.setInstallerName(getPackageNameNormalized(installerPackageName));
+            builder.setInstallerCertificate(
+                    getInstallerCertificateFingerprint(installerPackageName));
+            builder.setIsPreInstalled(isSystemApp(packageName));
+
+            AppInstallMetadata appInstallMetadata = builder.build();
+
+            Slog.i(TAG, "To be verified: " + appInstallMetadata);
+            IntegrityCheckResult result =
+                    mEvaluationEngine.evaluate(
+                            appInstallMetadata, getAllowedInstallers(packageInfo));
+            Slog.i(
+                    TAG,
+                    "Integrity check result: "
+                            + result.getEffect()
+                            + " due to "
+                            + result.getRule());
+            mPackageManagerInternal.setIntegrityVerificationResult(
+                    verificationId,
+                    result.getEffect() == IntegrityCheckResult.Effect.ALLOW
+                            ? PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW
+                            : PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
+        } catch (IllegalArgumentException e) {
+            // This exception indicates something is wrong with the input passed by package manager.
+            // e.g., someone trying to trick the system. We block installs in this case.
+            Slog.e(TAG, "Invalid input to integrity verification", e);
+            mPackageManagerInternal.setIntegrityVerificationResult(
+                    verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
+        } catch (Exception e) {
+            // Other exceptions indicate an error within the integrity component implementation and
+            // we allow them.
+            Slog.e(TAG, "Error handling integrity verification", e);
+            mPackageManagerInternal.setIntegrityVerificationResult(
+                    verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
+        }
+    }
+
+    /**
+     * 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.
+     */
+    @Nullable
+    private String getInstallerPackageName(Intent intent) {
+        String installer =
+                intent.getStringExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE);
+        if (installer == null) {
+            return ADB_INSTALLER;
+        }
+        int installerUid = intent.getIntExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_UID, -1);
+        if (installerUid < 0) {
+            Slog.e(
+                    TAG,
+                    "Installer cannot be determined: installer: "
+                            + installer
+                            + " installer UID: "
+                            + installerUid);
+            return UNKNOWN_INSTALLER;
+        }
+
+        try {
+            int actualInstallerUid =
+                    mContext.getPackageManager().getPackageUid(installer, /* flags= */ 0);
+            if (actualInstallerUid != installerUid) {
+                // Installer package name can be faked but the installerUid cannot.
+                Slog.e(
+                        TAG,
+                        "Installer "
+                                + installer
+                                + " has UID "
+                                + actualInstallerUid
+                                + " which doesn't match alleged installer UID "
+                                + installerUid);
+                return UNKNOWN_INSTALLER;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(TAG, "Installer package " + installer + " not found.");
+            return UNKNOWN_INSTALLER;
+        }
+
+        // At this time we can trust "installer".
+
+        // A common way for apps to install packages is to send an intent to PackageInstaller. In
+        // that case, the installer will always show up as PackageInstaller which is not what we
+        // want.
+        if (installer.equals(PACKAGE_INSTALLER)) {
+            int originatingUid = intent.getIntExtra(EXTRA_ORIGINATING_UID, -1);
+            if (originatingUid < 0) {
+                Slog.e(TAG, "Installer is package installer but originating UID not found.");
+                return UNKNOWN_INSTALLER;
+            }
+            String[] installerPackages =
+                    mContext.getPackageManager().getPackagesForUid(originatingUid);
+            if (installerPackages == null || installerPackages.length == 0) {
+                Slog.e(TAG, "No package found associated with originating UID " + originatingUid);
+                return UNKNOWN_INSTALLER;
+            }
+            // In the case of multiple package sharing a UID, we just return the first one.
+            return installerPackages[0];
+        }
+
+        return installer;
+    }
+
+    /** We will use the SHA256 digest of a package name if it is more than 32 bytes long. */
+    private String getPackageNameNormalized(String packageName) {
+        if (packageName.length() <= 32) {
+            return packageName;
+        }
+
+        try {
+            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
+            byte[] hashBytes = messageDigest.digest(packageName.getBytes(StandardCharsets.UTF_8));
+            return getHexDigest(hashBytes);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("SHA-256 algorithm not found", e);
+        }
+    }
+
+    private String getCertificateFingerprint(@NonNull PackageInfo packageInfo) {
+        return getFingerprint(getSignature(packageInfo));
+    }
+
+    private String getInstallerCertificateFingerprint(String installer) {
+        if (installer.equals(ADB_INSTALLER) || installer.equals(UNKNOWN_INSTALLER)) {
+            return INSTALLER_CERT_NOT_APPLICABLE;
+        }
+        try {
+            PackageInfo installerInfo =
+                    mContext.getPackageManager()
+                            .getPackageInfo(installer, PackageManager.GET_SIGNATURES);
+            return getCertificateFingerprint(installerInfo);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.i(TAG, "Installer package " + installer + " not found.");
+            return "";
+        }
+    }
+
+    /** Get the allowed installers and their associated certificate hashes from <meta-data> tag. */
+    private Map<String, String> getAllowedInstallers(@NonNull PackageInfo packageInfo) {
+        Map<String, String> packageCertMap = new HashMap<>();
+        if (packageInfo.applicationInfo != null && packageInfo.applicationInfo.metaData != null) {
+            Bundle metaData = packageInfo.applicationInfo.metaData;
+            String allowedInstallers = metaData.getString(ALLOWED_INSTALLERS_METADATA_NAME);
+            if (allowedInstallers != null) {
+                // parse the metadata for certs.
+                String[] installerCertPairs = allowedInstallers.split(ALLOWED_INSTALLER_DELIMITER);
+                for (String packageCertPair : installerCertPairs) {
+                    String[] packageAndCert =
+                            packageCertPair.split(INSTALLER_PACKAGE_CERT_DELIMITER);
+                    if (packageAndCert.length == 2) {
+                        String packageName = packageAndCert[0];
+                        String cert = packageAndCert[1];
+                        packageCertMap.put(packageName, cert);
+                    }
+                }
+            }
+        }
+
+        Slog.i("DEBUG", "allowed installers map " + packageCertMap);
+        return packageCertMap;
+    }
+
+    private boolean getPreInstalled(String packageName) {
+        try {
+            PackageInfo existingPackageInfo =
+                    mContext.getPackageManager().getPackageInfo(packageName, 0);
+            return existingPackageInfo.applicationInfo != null
+                    && existingPackageInfo.applicationInfo.isSystemApp();
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    private static Signature getSignature(@NonNull PackageInfo packageInfo) {
+        if (packageInfo.signatures == null || packageInfo.signatures.length < 1) {
+            throw new IllegalArgumentException("Package signature not found in " + packageInfo);
+        }
+        // Only the first element is guaranteed to be present.
+        return packageInfo.signatures[0];
+    }
+
+    private static String getFingerprint(Signature cert) {
+        InputStream input = new ByteArrayInputStream(cert.toByteArray());
+
+        CertificateFactory factory;
+        try {
+            factory = CertificateFactory.getInstance("X509");
+        } catch (CertificateException e) {
+            throw new RuntimeException("Error getting CertificateFactory", e);
+        }
+        X509Certificate certificate = null;
+        try {
+            if (factory != null) {
+                certificate = (X509Certificate) factory.generateCertificate(input);
+            }
+        } catch (CertificateException e) {
+            throw new RuntimeException("Error getting X509Certificate", e);
+        }
+
+        if (certificate == null) {
+            throw new RuntimeException("X509 Certificate not found");
+        }
+
+        try {
+            MessageDigest digest = MessageDigest.getInstance("SHA-256");
+            byte[] publicKey = digest.digest(certificate.getEncoded());
+            return getHexDigest(publicKey);
+        } catch (NoSuchAlgorithmException | CertificateEncodingException e) {
+            throw new IllegalArgumentException("Error error computing fingerprint", e);
+        }
+    }
+
+    private PackageInfo getPackageArchiveInfo(Uri dataUri) {
+        File installationPath = getInstallationPath(dataUri);
+        if (installationPath == null) {
+            throw new IllegalArgumentException("Installation path is null, package not found");
+        }
+        PackageInfo packageInfo;
+        try {
+            // The installation path will be a directory for a multi-apk install on L+
+            if (installationPath.isDirectory()) {
+                packageInfo = getMultiApkInfo(installationPath);
+            } else {
+                packageInfo =
+                        mContext.getPackageManager()
+                                .getPackageArchiveInfo(
+                                        installationPath.getPath(),
+                                        PackageManager.GET_SIGNATURES
+                                                | PackageManager.GET_META_DATA);
+            }
+            return packageInfo;
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Exception reading " + dataUri, e);
+        }
+    }
+
+    private PackageInfo getMultiApkInfo(File multiApkDirectory) {
+        // The base apk will normally be called base.apk
+        File baseFile = new File(multiApkDirectory, BASE_APK_FILE);
+        PackageInfo basePackageInfo =
+                mContext.getPackageManager()
+                        .getPackageArchiveInfo(
+                                baseFile.getAbsolutePath(), PackageManager.GET_SIGNATURES);
+
+        if (basePackageInfo == null) {
+            for (File apkFile : multiApkDirectory.listFiles()) {
+                if (apkFile.isDirectory()) {
+                    continue;
+                }
+
+                // If we didn't find a base.apk, then try to parse each apk until we find the one
+                // that succeeds.
+                basePackageInfo =
+                        mContext.getPackageManager()
+                                .getPackageArchiveInfo(
+                                        apkFile.getAbsolutePath(),
+                                        PackageManager.GET_SIGNING_CERTIFICATES);
+                if (basePackageInfo != null) {
+                    Slog.i(TAG, "Found package info from " + apkFile);
+                    break;
+                }
+            }
+        }
+
+        if (basePackageInfo == null) {
+            throw new IllegalArgumentException(
+                    "Base package info cannot be found from installation directory");
+        }
+
+        return basePackageInfo;
+    }
+
+    private File getInstallationPath(Uri dataUri) {
+        if (dataUri == null) {
+            throw new IllegalArgumentException("Null data uri");
+        }
+
+        String scheme = dataUri.getScheme();
+        if (!"file".equalsIgnoreCase(scheme)) {
+            throw new IllegalArgumentException("Unsupported scheme for " + dataUri);
+        }
+
+        File installationPath = new File(dataUri.getPath());
+        if (!installationPath.exists()) {
+            throw new IllegalArgumentException("Cannot find file for " + dataUri);
+        }
+        if (!installationPath.canRead()) {
+            throw new IllegalArgumentException("Cannot read file for " + dataUri);
+        }
+        return installationPath;
+    }
+
+    private String getCallerPackageNameOrThrow() {
+        final String[] allowedRuleProviders =
+                mContext.getResources()
+                        .getStringArray(R.array.config_integrityRuleProviderPackages);
+        for (String packageName : allowedRuleProviders) {
+            try {
+                // At least in tests, getPackageUid gives "NameNotFound" but getPackagesFromUid
+                // give the correct package name.
+                int uid = mContext.getPackageManager().getPackageUid(packageName, 0);
+                if (uid == Binder.getCallingUid()) {
+                    // Caller is allowed in the config.
+                    if (isSystemApp(packageName)) {
+                        return packageName;
+                    }
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // Ignore the exception. We don't expect the app to be necessarily installed.
+                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.");
+    }
+
+    private boolean isSystemApp(String packageName) {
+        try {
+            PackageInfo existingPackageInfo =
+                    mContext.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);
+            return existingPackageInfo.applicationInfo != null
+                    && existingPackageInfo.applicationInfo.isSystemApp();
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/integrity/IntegrityFileManager.java b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
new file mode 100644
index 0000000..bdf0279
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
@@ -0,0 +1,169 @@
+/*
+ * 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.integrity;
+
+import android.annotation.Nullable;
+import android.content.integrity.AppInstallMetadata;
+import android.content.integrity.Rule;
+import android.os.Environment;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.integrity.model.RuleMetadata;
+import com.android.server.integrity.parser.RuleBinaryParser;
+import com.android.server.integrity.parser.RuleMetadataParser;
+import com.android.server.integrity.parser.RuleParseException;
+import com.android.server.integrity.parser.RuleParser;
+import com.android.server.integrity.serializer.RuleBinarySerializer;
+import com.android.server.integrity.serializer.RuleMetadataSerializer;
+import com.android.server.integrity.serializer.RuleSerializeException;
+import com.android.server.integrity.serializer.RuleSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+
+/** Abstraction over the underlying storage of rules and other metadata. */
+public class IntegrityFileManager {
+    private static final String TAG = "IntegrityFileManager";
+
+    // TODO: this is a prototype implementation of this class. Thus no tests are included.
+    //  Implementing rule indexing will likely overhaul this class and more tests should be included
+    //  then.
+
+    private static final String METADATA_FILE = "metadata";
+    private static final String RULES_FILE = "rules";
+    private static final Object RULES_LOCK = new Object();
+
+    private static IntegrityFileManager sInstance = null;
+
+    private final RuleParser mRuleParser;
+    private final RuleSerializer mRuleSerializer;
+
+    // mRulesDir contains data of the actual rules currently stored.
+    private final File mRulesDir;
+    // mStagingDir is used to store the temporary rules / metadata during updating, since we want to
+    // update rules atomically.
+    private final File mStagingDir;
+
+    @Nullable private RuleMetadata mRuleMetadataCache;
+
+    /** Get the singleton instance of this class. */
+    public static synchronized IntegrityFileManager getInstance() {
+        if (sInstance == null) {
+            sInstance = new IntegrityFileManager();
+        }
+        return sInstance;
+    }
+
+    private IntegrityFileManager() {
+        this(
+                new RuleBinaryParser(),
+                new RuleBinarySerializer(),
+                Environment.getDataSystemDirectory());
+    }
+
+    @VisibleForTesting
+    IntegrityFileManager(RuleParser ruleParser, RuleSerializer ruleSerializer, File dataDir) {
+        mRuleParser = ruleParser;
+        mRuleSerializer = ruleSerializer;
+
+        mRulesDir = new File(dataDir, "integrity_rules");
+        mStagingDir = new File(dataDir, "integrity_staging");
+
+        if (!mStagingDir.mkdirs() && mRulesDir.mkdirs()) {
+            Slog.e(TAG, "Error creating staging and rules directory");
+            // TODO: maybe throw an exception?
+        }
+
+        File metadataFile = new File(mRulesDir, METADATA_FILE);
+        if (metadataFile.exists()) {
+            try (FileInputStream inputStream = new FileInputStream(metadataFile)) {
+                mRuleMetadataCache = RuleMetadataParser.parse(inputStream);
+            } catch (Exception e) {
+                Slog.e(TAG, "Error reading metadata file.", e);
+            }
+        }
+    }
+
+    /** Write rules to persistent storage. */
+    public void writeRules(String version, String ruleProvider, List<Rule> rules)
+            throws IOException, RuleSerializeException {
+        try {
+            writeMetadata(mStagingDir, ruleProvider, version);
+        } catch (IOException e) {
+            Slog.e(TAG, "Error writing metadata.", e);
+            // We don't consider this fatal so we continue execution.
+        }
+
+        try (FileOutputStream fileOutputStream =
+                new FileOutputStream(new File(mStagingDir, RULES_FILE))) {
+            mRuleSerializer.serialize(rules, Optional.empty(), fileOutputStream);
+        }
+
+        switchStagingRulesDir();
+    }
+
+    /**
+     * Read rules from persistent storage.
+     *
+     * @param appInstallMetadata information about the install used to select rules to read
+     */
+    public List<Rule> readRules(AppInstallMetadata appInstallMetadata)
+            throws IOException, RuleParseException {
+        // TODO: select rules by index
+        synchronized (RULES_LOCK) {
+            try (FileInputStream inputStream =
+                    new FileInputStream(new File(mRulesDir, RULES_FILE))) {
+                List<Rule> rules = mRuleParser.parse(inputStream);
+                return rules;
+            }
+        }
+    }
+
+    /** Read the metadata of the current rules in storage. */
+    @Nullable
+    public RuleMetadata readMetadata() {
+        return mRuleMetadataCache;
+    }
+
+    private void switchStagingRulesDir() throws IOException {
+        synchronized (RULES_LOCK) {
+            File tmpDir = new File(Environment.getDataSystemDirectory(), "temp");
+
+            if (!(mRulesDir.renameTo(tmpDir)
+                    && mStagingDir.renameTo(mRulesDir)
+                    && tmpDir.renameTo(mStagingDir))) {
+                throw new IOException("Error switching staging/rules directory");
+            }
+        }
+    }
+
+    private void writeMetadata(File directory, String ruleProvider, String version)
+            throws IOException {
+        mRuleMetadataCache = new RuleMetadata(ruleProvider, version);
+
+        File metadataFile = new File(directory, METADATA_FILE);
+
+        try (FileOutputStream outputStream = new FileOutputStream(metadataFile)) {
+            RuleMetadataSerializer.serialize(mRuleMetadataCache, outputStream);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/integrity/IntegrityUtils.java b/services/core/java/com/android/server/integrity/IntegrityUtils.java
new file mode 100644
index 0000000..f49c675
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/IntegrityUtils.java
@@ -0,0 +1,79 @@
+/*
+ * 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.integrity;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+/** Utils class for simple operations used in integrity module. */
+public class IntegrityUtils {
+
+    private static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
+
+    /**
+     * Obtain the raw bytes from hex encoded string.
+     *
+     * @throws IllegalArgumentException if {@code hexDigest} is not a valid hex encoding of some
+     *     bytes
+     */
+    public static byte[] getBytesFromHexDigest(String hexDigest) {
+        checkArgument(
+                hexDigest.length() % 2 == 0,
+                "Invalid hex encoding " + hexDigest + ": must have even length");
+
+        byte[] rawBytes = new byte[hexDigest.length() / 2];
+        for (int i = 0; i < rawBytes.length; i++) {
+            int upperNibble = hexDigest.charAt(2 * i);
+            int lowerNibble = hexDigest.charAt(2 * i + 1);
+            rawBytes[i] = (byte) ((hexToDec(upperNibble) << 4) | hexToDec(lowerNibble));
+        }
+        return rawBytes;
+    }
+
+    /** Obtain hex encoded string from raw bytes. */
+    public static String getHexDigest(byte[] rawBytes) {
+        char[] hexChars = new char[rawBytes.length * 2];
+
+        for (int i = 0; i < rawBytes.length; i++) {
+            int upperNibble = (rawBytes[i] >>> 4) & 0xF;
+            int lowerNibble = rawBytes[i] & 0xF;
+            hexChars[i * 2] = decToHex(upperNibble);
+            hexChars[i * 2 + 1] = decToHex(lowerNibble);
+        }
+        return new String(hexChars);
+    }
+
+    private static int hexToDec(int hexChar) {
+        if (hexChar >= '0' && hexChar <= '9') {
+            return hexChar - '0';
+        }
+        if (hexChar >= 'a' && hexChar <= 'f') {
+            return hexChar - 'a' + 10;
+        }
+        if (hexChar >= 'A' && hexChar <= 'F') {
+            return hexChar - 'A' + 10;
+        }
+        throw new IllegalArgumentException("Invalid hex char " + hexChar);
+    }
+
+    private static char decToHex(int dec) {
+        if (dec >= 0 && dec < HEX_CHARS.length) {
+            return HEX_CHARS[dec];
+        }
+
+        throw new IllegalArgumentException("Invalid dec value to be converted to hex digit " + dec);
+    }
+}
diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
index b8202b6..0ea6efc 100644
--- a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
+++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
@@ -18,11 +18,14 @@
 
 import android.content.integrity.AppInstallMetadata;
 import android.content.integrity.Rule;
+import android.util.Slog;
 
+import com.android.server.integrity.IntegrityFileManager;
 import com.android.server.integrity.model.IntegrityCheckResult;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * The engine used to evaluate rules against app installs.
@@ -30,17 +33,23 @@
  * <p>Every app install is evaluated against rules (pushed by the verifier) by the evaluation engine
  * to allow/block that install.
  */
-public final class RuleEvaluationEngine {
+public class RuleEvaluationEngine {
     private static final String TAG = "RuleEvaluation";
 
     // The engine for loading rules, retrieving metadata for app installs, and evaluating app
     // installs against rules.
     private static RuleEvaluationEngine sRuleEvaluationEngine;
 
+    private final IntegrityFileManager mIntegrityFileManager;
+
+    private RuleEvaluationEngine(IntegrityFileManager integrityFileManager) {
+        mIntegrityFileManager = integrityFileManager;
+    }
+
     /** Provide a singleton instance of the rule evaluation engine. */
     public static synchronized RuleEvaluationEngine getRuleEvaluationEngine() {
         if (sRuleEvaluationEngine == null) {
-            return new RuleEvaluationEngine();
+            return new RuleEvaluationEngine(IntegrityFileManager.getInstance());
         }
         return sRuleEvaluationEngine;
     }
@@ -52,13 +61,18 @@
      *     against.
      * @return result of the integrity check
      */
-    public IntegrityCheckResult evaluate(AppInstallMetadata appInstallMetadata) {
+    public IntegrityCheckResult evaluate(
+            AppInstallMetadata appInstallMetadata, Map<String, String> allowedInstallers) {
         List<Rule> rules = loadRules(appInstallMetadata);
         return RuleEvaluator.evaluateRules(rules, appInstallMetadata);
     }
 
     private List<Rule> loadRules(AppInstallMetadata appInstallMetadata) {
-        // TODO: Load rules
-        return new ArrayList<>();
+        try {
+            return mIntegrityFileManager.readRules(appInstallMetadata);
+        } catch (Exception e) {
+            Slog.e(TAG, "Error loading rules.", e);
+            return new ArrayList<>();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/integrity/model/RuleMetadata.java b/services/core/java/com/android/server/integrity/model/RuleMetadata.java
new file mode 100644
index 0000000..6b582ae
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/model/RuleMetadata.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.integrity.model;
+
+import android.annotation.Nullable;
+
+/** Data class containing relevant metadata associated with a rule set. */
+public class RuleMetadata {
+
+    private final String mRuleProvider;
+    private final String mVersion;
+
+    public RuleMetadata(String ruleProvider, String version) {
+        mRuleProvider = ruleProvider;
+        mVersion = version;
+    }
+
+    @Nullable
+    public String getRuleProvider() {
+        return mRuleProvider;
+    }
+
+    @Nullable
+    public String getVersion() {
+        return mVersion;
+    }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java b/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
new file mode 100644
index 0000000..28d2e69
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
@@ -0,0 +1,67 @@
+/*
+ * 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.integrity.parser;
+
+import android.annotation.Nullable;
+import android.util.Xml;
+
+import com.android.server.integrity.model.RuleMetadata;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+/** Helper class for parsing rule metadata. */
+public class RuleMetadataParser {
+
+    public static final String RULE_PROVIDER_TAG = "P";
+    public static final String VERSION_TAG = "V";
+
+    /** Parse the rule metadata from an input stream. */
+    @Nullable
+    public static RuleMetadata parse(InputStream inputStream)
+            throws XmlPullParserException, IOException {
+
+        String ruleProvider = "";
+        String version = "";
+
+        XmlPullParser xmlPullParser = Xml.newPullParser();
+        xmlPullParser.setInput(inputStream, StandardCharsets.UTF_8.name());
+
+        int eventType;
+        while ((eventType = xmlPullParser.next()) != XmlPullParser.END_DOCUMENT) {
+            if (eventType == XmlPullParser.START_TAG) {
+                String tag = xmlPullParser.getName();
+                switch (tag) {
+                    case RULE_PROVIDER_TAG:
+                        ruleProvider = xmlPullParser.nextText();
+                        break;
+                    case VERSION_TAG:
+                        version = xmlPullParser.nextText();
+                        break;
+                    default:
+                        throw new IllegalStateException("Unknown tag in metadata: " + tag);
+                }
+            }
+        }
+
+        return new RuleMetadata(ruleProvider, version);
+    }
+}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
new file mode 100644
index 0000000..5c51f31
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
@@ -0,0 +1,52 @@
+/*
+ * 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.integrity.serializer;
+
+import static com.android.server.integrity.parser.RuleMetadataParser.RULE_PROVIDER_TAG;
+import static com.android.server.integrity.parser.RuleMetadataParser.VERSION_TAG;
+
+import android.util.Xml;
+
+import com.android.server.integrity.model.RuleMetadata;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+/** Helper class for writing rule metadata. */
+public class RuleMetadataSerializer {
+    /** Serialize the rule metadata to an output stream. */
+    public static void serialize(RuleMetadata ruleMetadata, OutputStream outputStream)
+            throws IOException {
+        XmlSerializer xmlSerializer = Xml.newSerializer();
+        xmlSerializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
+
+        serializeTaggedValue(xmlSerializer, RULE_PROVIDER_TAG, ruleMetadata.getRuleProvider());
+        serializeTaggedValue(xmlSerializer, VERSION_TAG, ruleMetadata.getVersion());
+
+        xmlSerializer.endDocument();
+    }
+
+    private static void serializeTaggedValue(XmlSerializer xmlSerializer, String tag, String value)
+            throws IOException {
+        xmlSerializer.startTag(/* namespace= */ null, tag);
+        xmlSerializer.text(value);
+        xmlSerializer.endTag(/* namespace= */ null, tag);
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 51fcbb0..bcc4c1f 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -27,8 +27,6 @@
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
 import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
-import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT;
-import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
 import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
 import static com.android.internal.widget.LockPatternUtils.USER_FRP;
@@ -2582,23 +2580,12 @@
             return type == PersistentData.TYPE_SP || type == PersistentData.TYPE_SP_WEAVER;
         }
         long handle = getSyntheticPasswordHandleLocked(userId);
-        // This is a global setting
-        long enabled = getLong(SYNTHETIC_PASSWORD_ENABLED_KEY,
-                SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT, UserHandle.USER_SYSTEM);
-      return enabled != 0 && handle != SyntheticPasswordManager.DEFAULT_HANDLE;
+        return handle != SyntheticPasswordManager.DEFAULT_HANDLE;
     }
 
     @VisibleForTesting
     protected boolean shouldMigrateToSyntheticPasswordLocked(int userId) {
-        long handle = getSyntheticPasswordHandleLocked(userId);
-        // This is a global setting
-        long enabled = getLong(SYNTHETIC_PASSWORD_ENABLED_KEY,
-                SYNTHETIC_PASSWORD_ENABLED_BY_DEFAULT, UserHandle.USER_SYSTEM);
-        return enabled != 0 && handle == SyntheticPasswordManager.DEFAULT_HANDLE;
-    }
-
-    private void enableSyntheticPasswordLocked() {
-        setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1, UserHandle.USER_SYSTEM);
+        return true;
     }
 
     private VerifyCredentialResponse spBasedDoVerifyCredential(LockscreenCredential userCredential,
@@ -2937,7 +2924,6 @@
     private long addEscrowToken(byte[] token, int userId, EscrowTokenStateChangeCallback callback) {
         if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId);
         synchronized (mSpManager) {
-            enableSyntheticPasswordLocked();
             // Migrate to synthetic password based credentials if the user has no password,
             // the token can then be activated immediately.
             AuthenticationToken auth = null;
diff --git a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
index eb706d7..1d39177 100644
--- a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
+++ b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
@@ -240,8 +240,7 @@
                     if (!mPrevActiveAudioPlaybackConfigs.containsKey(
                             config.getPlayerInterfaceId())) {
                         if (DEBUG) {
-                            Log.d(TAG, "Found a new active media playback. "
-                                    + AudioPlaybackConfiguration.toLogFriendlyString(config));
+                            Log.d(TAG, "Found a new active media playback. " + config);
                         }
                         // New active audio playback.
                         int index = mSortedAudioPlaybackClientUids.indexOf(uid);
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 9a49c16..0483431 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
+import android.media.RouteSessionInfo;
 import android.os.Bundle;
 
 import java.util.Objects;
@@ -42,6 +43,8 @@
         mCallback = callback;
     }
 
+    public abstract void requestCreateSession(String packageName, String routeId,
+            String controlCategory, int requestId);
     public abstract void requestSelectRoute(String packageName, String routeId, int seq);
     public abstract void unselectRoute(String packageName, String routeId);
     public abstract void sendControlRequest(MediaRoute2Info route, Intent request);
@@ -82,5 +85,7 @@
         void onRouteSelected(@NonNull MediaRoute2ProviderProxy provider,
                 @NonNull String clientPackageName, @NonNull MediaRoute2Info route,
                 @Nullable Bundle controlHints, int seq);
+        void onSessionCreated(@NonNull MediaRoute2Provider provider,
+                @Nullable RouteSessionInfo sessionInfo, int requestId);
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index a5abb18..893747b 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -17,6 +17,7 @@
 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,6 +27,7 @@
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2ProviderService;
+import android.media.RouteSessionInfo;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -74,6 +76,16 @@
     }
 
     @Override
+    public void requestCreateSession(String packageName, String routeId, String controlCategory,
+            int requestId) {
+        if (mConnectionReady) {
+            mActiveConnection.requestCreateSession(packageName, routeId, controlCategory,
+                    requestId);
+            updateBinding();
+        }
+    }
+
+    @Override
     public void requestSelectRoute(String packageName, String routeId, int seq) {
         if (mConnectionReady) {
             mActiveConnection.requestSelectRoute(packageName, routeId, seq);
@@ -268,6 +280,14 @@
         mCallback.onRouteSelected(this, packageName, route, controlHints, seq);
     }
 
+    private void onSessionCreated(Connection connection, @Nullable RouteSessionInfo sessionInfo,
+            int requestId) {
+        if (mActiveConnection != connection) {
+            return;
+        }
+        mCallback.onSessionCreated(this, sessionInfo, requestId);
+    }
+
     private void disconnect() {
         if (mActiveConnection != null) {
             mConnectionReady = false;
@@ -308,6 +328,15 @@
             mClient.dispose();
         }
 
+        public void requestCreateSession(String packageName, String routeId, String controlCategory,
+                int requestId) {
+            try {
+                mProvider.requestCreateSession(packageName, routeId, controlCategory, requestId);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Failed to deliver request to create a session.", ex);
+            }
+        }
+
         public void requestSelectRoute(String packageName, String routeId, int seq) {
             try {
                 mProvider.requestSelectRoute(packageName, routeId, seq);
@@ -361,6 +390,11 @@
             mHandler.post(() -> onRouteSelected(Connection.this,
                     packageName, routeId, controlHints, seq));
         }
+
+        void postSessionCreated(@Nullable RouteSessionInfo sessionInfo, int requestId) {
+            mHandler.post(() -> onSessionCreated(Connection.this, sessionInfo,
+                    requestId));
+        }
     }
 
     private static final class ProviderClient extends IMediaRoute2ProviderClient.Stub  {
@@ -391,5 +425,12 @@
             }
         }
 
+        @Override
+        public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, int requestId) {
+            Connection connection = mConnectionRef.get();
+            if (connection != null) {
+                connection.postSessionCreated(sessionInfo, requestId);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index e7b8860..a711863 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -29,6 +29,7 @@
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRouter2;
+import android.media.RouteSessionInfo;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -55,6 +56,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * TODO: Merge this to MediaRouterService once it's finished.
@@ -166,6 +168,24 @@
         }
     }
 
+    public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route,
+            String controlCategory, int requestId) {
+        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);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     public void sendControlRequest(@NonNull IMediaRouter2Client client,
             @NonNull MediaRoute2Info route, @NonNull Intent request) {
         Objects.requireNonNull(client, "client must not be null");
@@ -198,18 +218,6 @@
         }
     }
 
-    public void requestSelectRoute2(@NonNull IMediaRouter2Client client,
-            @Nullable MediaRoute2Info route) {
-        final long token = Binder.clearCallingIdentity();
-        try {
-            synchronized (mLock) {
-                requestSelectRoute2Locked(mAllClientRecords.get(client.asBinder()), false, route);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
     public void requestSetVolume2(IMediaRouter2Client client, MediaRoute2Info route, int volume) {
         Objects.requireNonNull(client, "client must not be null");
         Objects.requireNonNull(route, "route must not be null");
@@ -353,6 +361,19 @@
         }
     }
 
+    private void requestCreateSessionLocked(@NonNull IMediaRouter2Client client,
+            @NonNull MediaRoute2Info route, @NonNull String controlCategory, int requestId) {
+        final IBinder binder = client.asBinder();
+        final Client2Record clientRecord = mAllClientRecords.get(binder);
+
+        if (clientRecord != null) {
+            clientRecord.mUserRecord.mHandler.sendMessage(
+                    obtainMessage(UserHandler::requestCreateSessionOnHandler,
+                            clientRecord.mUserRecord.mHandler,
+                            clientRecord, route, controlCategory, requestId));
+        }
+    }
+
     private void requestSelectRoute2Locked(Client2Record clientRecord, boolean selectedByManager,
             MediaRoute2Info route) {
         if (clientRecord != null) {
@@ -569,6 +590,7 @@
             mHandler = new UserHandler(MediaRouter2ServiceImpl.this, this);
         }
 
+        // TODO: This assumes that only one client exists in a package. Is it true?
         Client2Record findClientRecordLocked(String packageName) {
             for (Client2Record clientRecord : mClientRecords) {
                 if (TextUtils.equals(clientRecord.mPackageName, packageName)) {
@@ -667,10 +689,12 @@
         private final SystemMediaRoute2Provider mSystemProvider;
         private final ArrayList<MediaRoute2Provider> mMediaProviders =
                 new ArrayList<>();
-        private final List<MediaRoute2ProviderInfo> mProviderInfos = new ArrayList<>();
+
+        private final List<MediaRoute2ProviderInfo> mLastProviderInfos = new ArrayList<>();
+        private final CopyOnWriteArrayList<SessionCreationRequest> mSessionCreationRequests =
+                new CopyOnWriteArrayList<>();
 
         private boolean mRunning;
-        private boolean mProviderInfosUpdateScheduled;
 
         UserHandler(MediaRouter2ServiceImpl service, UserRecord userRecord) {
             super(Looper.getMainLooper(), null, true);
@@ -721,23 +745,30 @@
                     controlHints, seq));
         }
 
+        @Override
+        public void onSessionCreated(@NonNull MediaRoute2Provider provider,
+                @Nullable RouteSessionInfo sessionInfo, int requestId) {
+            sendMessage(PooledLambda.obtainMessage(UserHandler::handleCreateSessionResultOnHandler,
+                    this, provider, sessionInfo, requestId));
+        }
+
         private void updateProvider(MediaRoute2Provider provider) {
             int providerIndex = getProviderInfoIndex(provider.getUniqueId());
             MediaRoute2ProviderInfo providerInfo = provider.getProviderInfo();
             MediaRoute2ProviderInfo prevInfo =
-                    (providerIndex < 0) ? null : mProviderInfos.get(providerIndex);
+                    (providerIndex < 0) ? null : mLastProviderInfos.get(providerIndex);
 
             if (Objects.equals(prevInfo, providerInfo)) return;
 
             if (prevInfo == null) {
-                mProviderInfos.add(providerInfo);
+                mLastProviderInfos.add(providerInfo);
                 Collection<MediaRoute2Info> addedRoutes = providerInfo.getRoutes();
                 if (addedRoutes.size() > 0) {
                     sendMessage(PooledLambda.obtainMessage(UserHandler::notifyRoutesAddedToClients,
                             this, getClients(), new ArrayList<>(addedRoutes)));
                 }
             } else if (providerInfo == null) {
-                mProviderInfos.remove(prevInfo);
+                mLastProviderInfos.remove(prevInfo);
                 Collection<MediaRoute2Info> removedRoutes = prevInfo.getRoutes();
                 if (removedRoutes.size() > 0) {
                     sendMessage(PooledLambda.obtainMessage(
@@ -745,7 +776,7 @@
                             this, getClients(), new ArrayList<>(removedRoutes)));
                 }
             } else {
-                mProviderInfos.set(providerIndex, providerInfo);
+                mLastProviderInfos.set(providerIndex, providerInfo);
                 List<MediaRoute2Info> addedRoutes = new ArrayList<>();
                 List<MediaRoute2Info> removedRoutes = new ArrayList<>();
                 List<MediaRoute2Info> changedRoutes = new ArrayList<>();
@@ -794,8 +825,8 @@
         }
 
         private int getProviderInfoIndex(String providerId) {
-            for (int i = 0; i < mProviderInfos.size(); i++) {
-                MediaRoute2ProviderInfo providerInfo = mProviderInfos.get(i);
+            for (int i = 0; i < mLastProviderInfos.size(); i++) {
+                MediaRoute2ProviderInfo providerInfo = mLastProviderInfos.get(i);
                 if (TextUtils.equals(providerInfo.getUniqueId(), providerId)) {
                     return i;
                 }
@@ -803,6 +834,94 @@
             return -1;
         }
 
+        private void requestCreateSessionOnHandler(Client2Record clientRecord,
+                MediaRoute2Info route, String controlCategory, int requestId) {
+
+            final MediaRoute2Provider provider = findProvider(route.getProviderId());
+            if (provider == null) {
+                Slog.w(TAG, "Ignoring session creation request since no provider found for"
+                        + " given route=" + route);
+                notifySessionCreationFailed(clientRecord, requestId);
+                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, requestId);
+                return;
+            }
+
+            // TODO: Apply timeout for each request (How many seconds should we wait?)
+            SessionCreationRequest request = new SessionCreationRequest(
+                    clientRecord, route, controlCategory, requestId);
+            mSessionCreationRequests.add(request);
+
+            provider.requestCreateSession(clientRecord.mPackageName, route.getId(),
+                    controlCategory, requestId);
+        }
+
+        private void handleCreateSessionResultOnHandler(
+                @NonNull MediaRoute2Provider provider, @Nullable RouteSessionInfo sessionInfo,
+                int 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 result for unknown request. "
+                        + "requestId=" + requestId + ", sessionInfo=" + sessionInfo);
+                return;
+            }
+
+            if (sessionInfo == null) {
+                // Failed
+                notifySessionCreationFailed(matchingRequest.mClientRecord, requestId);
+                return;
+            }
+
+            String originalRouteId = matchingRequest.mRoute.getId();
+            String originalCategory = matchingRequest.mControlCategory;
+            if (!sessionInfo.getSelectedRoutes().contains(originalRouteId)
+                    || !TextUtils.equals(originalCategory, sessionInfo.getControlCategory())) {
+                Slog.w(TAG, "Created session doesn't match the original request."
+                        + " originalRouteId=" + originalRouteId
+                        + ", originalCategory=" + originalCategory
+                        + ", requestId=" + requestId + ", sessionInfo=" + sessionInfo);
+                notifySessionCreationFailed(matchingRequest.mClientRecord, requestId);
+                return;
+            }
+
+            // Succeeded
+            notifySessionCreated(matchingRequest.mClientRecord, sessionInfo, requestId);
+            // TODO: Tell managers for the session creation
+        }
+
+        private void notifySessionCreated(Client2Record clientRecord, RouteSessionInfo sessionInfo,
+                int requestId) {
+            try {
+                clientRecord.mClient.notifySessionCreated(sessionInfo, requestId);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify client of the session creation."
+                        + " Client probably died.", ex);
+            }
+        }
+
+        private void notifySessionCreationFailed(Client2Record clientRecord, int requestId) {
+            try {
+                clientRecord.mClient.notifySessionCreated(/* sessionInfo= */ null, requestId);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify client of the session creation failure."
+                        + " Client probably died.", ex);
+            }
+        }
+
         private void updateSelectedRoute(MediaRoute2ProviderProxy provider,
                 String clientPackageName, MediaRoute2Info selectedRoute, Bundle controlHints,
                 int seq) {
@@ -858,6 +977,12 @@
                 clientRecord = mUserRecord.findClientRecordLocked(clientPackageName);
             }
 
+            if (clientRecord == null) {
+                Log.w(TAG, "The client has gone. packageName=" + clientPackageName
+                        + " selectingRoute=" + selectingRoute);
+                return;
+            }
+
             if (clientRecord.mSelectingRoute == null || !TextUtils.equals(
                     clientRecord.mSelectingRoute.getUniqueId(), selectingRoute.getUniqueId())) {
                 Log.w(TAG, "Ignoring invalid selectFallbackRoute call. "
@@ -951,7 +1076,7 @@
 
         private void notifyRoutesToClient(IMediaRouter2Client client) {
             List<MediaRoute2Info> routes = new ArrayList<>();
-            for (MediaRoute2ProviderInfo providerInfo : mProviderInfos) {
+            for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
                 routes.addAll(providerInfo.getRoutes());
             }
             if (routes.size() == 0) {
@@ -964,13 +1089,9 @@
             }
         }
 
+        // TODO: Remove notifyRouteSelected* methods
         private void notifyRouteSelectedToClient(IMediaRouter2Client client,
                 MediaRoute2Info route, int reason, Bundle controlHints) {
-            try {
-                client.notifyRouteSelected(route, reason, controlHints);
-            } catch (RemoteException ex) {
-                Slog.w(TAG, "Failed to notify routes selected. Client probably died.", ex);
-            }
         }
 
         private void notifyRoutesAddedToClients(List<IMediaRouter2Client> clients,
@@ -1008,7 +1129,7 @@
 
         private void notifyRoutesToManager(IMediaRouter2Manager manager) {
             List<MediaRoute2Info> routes = new ArrayList<>();
-            for (MediaRoute2ProviderInfo providerInfo : mProviderInfos) {
+            for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
                 routes.addAll(providerInfo.getRoutes());
             }
             if (routes.size() == 0) {
@@ -1085,5 +1206,21 @@
             }
             return null;
         }
+
+        final class SessionCreationRequest {
+            public final Client2Record mClientRecord;
+            public final MediaRoute2Info mRoute;
+            public final String mControlCategory;
+            public final int mRequestId;
+
+            SessionCreationRequest(@NonNull Client2Record clientRecord,
+                    @NonNull MediaRoute2Info route,
+                    @NonNull String controlCategory, int 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 9c99e8f..a280f91 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -457,8 +457,9 @@
 
     // Binder call
     @Override
-    public void requestSelectRoute2(IMediaRouter2Client client, MediaRoute2Info route) {
-        mService2.requestSelectRoute2(client, route);
+    public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route,
+            String controlCategory, int requestId) {
+        mService2.requestCreateSession(client, route, controlCategory, requestId);
     }
 
     // Binder call
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 4f64177..6c4c8d5 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -88,6 +88,12 @@
         initializeRoutes();
     }
 
+    @Override
+    public void requestCreateSession(String packageName, String routeId, String controlCategory,
+            int requestId) {
+        // Do nothing
+    }
+
     //TODO: implement method
     @Override
     public void requestSelectRoute(@NonNull String packageName, @NonNull String routeId, int seq) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 83d0ecd..2ebca88 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -1828,7 +1828,7 @@
             }
 
             String[] mergedSubscriberId = ArrayUtils.defeatNullable(
-                    tm.createForSubscriptionId(subId).getMergedSubscriberIdsFromGroup());
+                    tm.createForSubscriptionId(subId).getMergedImsisFromGroup());
             mergedSubscriberIdsList.add(mergedSubscriberId);
         }
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 47a41f5..e2dfa12 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -217,6 +217,7 @@
 
     public void systemReady() {
         mAppOps = mContext.getSystemService(AppOpsManager.class);
+        mStagingManager.systemReady();
 
         synchronized (mSessions) {
             readSessionsLocked();
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 688c34f..6c3eb31 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -22,10 +22,12 @@
 import android.apex.ApexInfoList;
 import android.apex.ApexSessionInfo;
 import android.apex.ApexSessionParams;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
@@ -774,6 +776,17 @@
         }
     }
 
+    void systemReady() {
+        // Register the receiver of boot completed intent for staging manager.
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context ctx, Intent intent) {
+                mPreRebootVerificationHandler.readyToStart();
+                ctx.unregisterReceiver(this);
+            }
+        }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
+    }
+
     private static class LocalIntentReceiverAsync {
         final Consumer<Intent> mConsumer;
 
@@ -824,6 +837,9 @@
     }
 
     private final class PreRebootVerificationHandler extends Handler {
+        // Hold session ids before handler gets ready to do the verification.
+        private IntArray mPendingSessionIds;
+        private boolean mIsReady;
 
         PreRebootVerificationHandler(Looper looper) {
             super(looper);
@@ -876,8 +892,26 @@
             }
         }
 
+        // Notify the handler that system is ready, and reschedule the pre-reboot verifications.
+        private synchronized void readyToStart() {
+            mIsReady = true;
+            if (mPendingSessionIds != null) {
+                for (int i = 0; i < mPendingSessionIds.size(); i++) {
+                    startPreRebootVerification(mPendingSessionIds.get(i));
+                }
+                mPendingSessionIds = null;
+            }
+        }
+
         // Method for starting the pre-reboot verification
-        private void startPreRebootVerification(int sessionId) {
+        private synchronized void startPreRebootVerification(int sessionId) {
+            if (!mIsReady) {
+                if (mPendingSessionIds == null) {
+                    mPendingSessionIds = new IntArray();
+                }
+                mPendingSessionIds.add(sessionId);
+                return;
+            }
             obtainMessage(MSG_PRE_REBOOT_VERIFICATION_START, sessionId, 0).sendToTarget();
         }
 
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 603b01a..6d6ec25 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -726,10 +726,12 @@
                 userId, STORAGE_PERMISSIONS);
 
         // TextClassifier Service
-        String textClassifierPackageName =
-                mContext.getPackageManager().getSystemTextClassifierPackageName();
-        if (!TextUtils.isEmpty(textClassifierPackageName)) {
-            grantPermissionsToSystemPackage(textClassifierPackageName, userId,
+        final String[] packages = mContext.getPackageManager().getSystemTextClassifierPackages();
+        if (packages.length > 0) {
+            // We have a list of supported system TextClassifier package names, the first one
+            // package is the default system TextClassifier service. Grant permissions to default
+            // TextClassifier Service.
+            grantPermissionsToSystemPackage(packages[0], userId,
                     COARSE_BACKGROUND_LOCATION_PERMISSIONS, CONTACTS_PERMISSIONS);
         }
 
@@ -998,7 +1000,7 @@
     private void revokeRuntimePermissions(String packageName, Set<String> permissions,
             boolean systemFixed, int userId) {
         PackageInfo pkg = getSystemPackageInfo(packageName);
-        if (ArrayUtils.isEmpty(pkg.requestedPermissions)) {
+        if (pkg == null || ArrayUtils.isEmpty(pkg.requestedPermissions)) {
             return;
         }
         Set<String> revokablePermissions = new ArraySet<>(Arrays.asList(pkg.requestedPermissions));
diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING
new file mode 100644
index 0000000..acf3f79
--- /dev/null
+++ b/services/core/java/com/android/server/power/TEST_MAPPING
@@ -0,0 +1,45 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsBatterySavingTestCases",
+      "options": [
+        {"exclude-annotation": "androidx.test.filters.LargeTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+      ]
+    },
+    {
+      "name": "FrameworksMockingServicesTests",
+      "options": [
+        {"include-filter": "com.android.server.power"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+      ]
+    },
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {"include-filter": "com.android.server.power"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {
+          "exclude-filter": "com.android.server.power.PowerManagerServiceTest#testWakefulnessAwake_ShouldWakeUpWhenPluggedIn"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsBatterySavingTestCases"
+    },
+    {
+      "name": "FrameworksMockingServicesTests",
+      "options": [
+        {"include-filter": "com.android.server.power"}
+      ]
+    },
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {"include-filter": "com.android.server.power"}
+      ]
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
new file mode 100644
index 0000000..0bbb179
--- /dev/null
+++ b/services/core/java/com/android/server/security/FileIntegrityService.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.security;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.SystemProperties;
+import android.security.Credentials;
+import android.security.IFileIntegrityService;
+import android.security.KeyStore;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * A {@link SystemService} that provides file integrity related operations.
+ * @hide
+ */
+public class FileIntegrityService extends SystemService {
+    private static final String TAG = "FileIntegrityService";
+
+    private static CertificateFactory sCertFactory;
+
+    private Collection<X509Certificate> mTrustedCertificates = new ArrayList<X509Certificate>();
+
+    private final IBinder mService = new IFileIntegrityService.Stub() {
+        @Override
+        public boolean isApkVeritySupported() {
+            return SystemProperties.getInt("ro.apk_verity.mode", 0) == 2;
+        }
+
+        @Override
+        public boolean isAppSourceCertificateTrusted(byte[] certificateBytes) {
+            enforceAnyCallingPermissions(
+                    android.Manifest.permission.REQUEST_INSTALL_PACKAGES,
+                    android.Manifest.permission.INSTALL_PACKAGES);
+            try {
+                if (!isApkVeritySupported()) {
+                    return false;
+                }
+
+                return mTrustedCertificates.contains(toCertificate(certificateBytes));
+            } catch (CertificateException e) {
+                Slog.e(TAG, "Failed to convert the certificate: " + e);
+                return false;
+            }
+        }
+
+        private void enforceAnyCallingPermissions(String ...permissions) {
+            for (String permission : permissions) {
+                if (getContext().checkCallingPermission(permission)
+                        == PackageManager.PERMISSION_GRANTED) {
+                    return;
+                }
+            }
+            throw new SecurityException("Insufficient permission");
+        }
+    };
+
+    public FileIntegrityService(final Context context) {
+        super(context);
+        try {
+            sCertFactory = CertificateFactory.getInstance("X.509");
+        } catch (CertificateException e) {
+            Slog.wtf(TAG, "Cannot get an instance of X.509 certificate factory");
+        }
+    }
+
+    @Override
+    public void onStart() {
+        loadAllCertificates();
+        publishBinderService(Context.FILE_INTEGRITY_SERVICE, mService);
+    }
+
+    private void loadAllCertificates() {
+        // A better alternative to load certificates would be to read from .fs-verity kernel
+        // keyring, which fsverity_init loads to during earlier boot time from the same sources
+        // below. But since the read operation from keyring is not provided in kernel, we need to
+        // duplicate the same loading logic here.
+
+        // Load certificates trusted by the device manufacturer.
+        loadCertificatesFromDirectory("/product/etc/security/fsverity");
+
+        // Load certificates trusted by the device owner.
+        loadCertificatesFromKeystore(KeyStore.getInstance());
+    }
+
+    private void loadCertificatesFromDirectory(String path) {
+        try {
+            File[] files = new File(path).listFiles();
+            if (files == null) {
+                return;
+            }
+
+            for (File cert : files) {
+                collectCertificate(Files.readAllBytes(cert.toPath()));
+            }
+        } catch (IOException e) {
+            Slog.wtf(TAG, "Failed to load fs-verity certificate from " + path, e);
+        }
+    }
+
+    private void loadCertificatesFromKeystore(KeyStore keystore) {
+        for (final String alias : keystore.list(Credentials.APP_SOURCE_CERTIFICATE,
+                    Process.FSVERITY_CERT_UID)) {
+            byte[] certificateBytes = keystore.get(Credentials.APP_SOURCE_CERTIFICATE + alias,
+                    Process.FSVERITY_CERT_UID, false /* suppressKeyNotFoundWarning */);
+            if (certificateBytes == null) {
+                Slog.w(TAG, "The retrieved fs-verity certificate is null, ignored " + alias);
+                continue;
+            }
+            collectCertificate(certificateBytes);
+        }
+    }
+
+    /**
+     * Tries to convert {@code bytes} into an X.509 certificate and store in memory.
+     * Errors need to be surpressed in order fo the next certificates to still be collected.
+     */
+    private void collectCertificate(@Nullable byte[] bytes) {
+        try {
+            mTrustedCertificates.add(toCertificate(bytes));
+        } catch (CertificateException | AssertionError e) {
+            Slog.e(TAG, "Invalid certificate, ignored: " + e);
+        }
+    }
+
+    /**
+     * Converts byte array into one X.509 certificate. If multiple certificate is defined, ignore
+     * the rest. The rational is to make it harder to smuggle.
+     */
+    @NonNull
+    private static X509Certificate toCertificate(@Nullable byte[] bytes)
+            throws CertificateException {
+        Certificate certificate = sCertFactory.generateCertificate(new ByteArrayInputStream(bytes));
+        if (!(certificate instanceof X509Certificate)) {
+            throw new CertificateException("Expected to contain an X.509 certificate");
+        }
+        return (X509Certificate) certificate;
+    }
+}
diff --git a/services/core/java/com/android/server/utils/quota/Categorizer.java b/services/core/java/com/android/server/utils/quota/Categorizer.java
index bf24991..0a45dab 100644
--- a/services/core/java/com/android/server/utils/quota/Categorizer.java
+++ b/services/core/java/com/android/server/utils/quota/Categorizer.java
@@ -25,6 +25,9 @@
  * @see Uptc
  */
 public interface Categorizer {
+    /** A {@link Categorizer} that always returns {@link Category.SINGLE_CATEGORY}. */
+    Categorizer SINGLE_CATEGORIZER = (userId, packageName, tag) -> Category.SINGLE_CATEGORY;
+
     /**
      * Return the {@link Category} that this UPTC belongs to.
      *
diff --git a/services/core/java/com/android/server/utils/quota/Category.java b/services/core/java/com/android/server/utils/quota/Category.java
index d8459d7..933c149 100644
--- a/services/core/java/com/android/server/utils/quota/Category.java
+++ b/services/core/java/com/android/server/utils/quota/Category.java
@@ -28,6 +28,12 @@
  * @see Uptc
  */
 public final class Category {
+    /**
+     * A {@link Category} that can be used if every app should be treated the same (given the same
+     * category).
+     */
+    public static final Category SINGLE_CATEGORY = new Category("SINGLE");
+
     @NonNull
     private final String mName;
 
diff --git a/services/core/java/com/android/server/utils/quota/QuotaTracker.java b/services/core/java/com/android/server/utils/quota/QuotaTracker.java
new file mode 100644
index 0000000..ef1f426
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/QuotaTracker.java
@@ -0,0 +1,661 @@
+/*
+ * 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.quota;
+
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import static com.android.server.utils.quota.Uptc.string;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArrayMap;
+import android.util.proto.ProtoOutputStream;
+import android.util.quota.QuotaTrackerProto;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.SystemServiceManager;
+
+import java.util.PriorityQueue;
+
+/**
+ * Base class for trackers that track whether an app has exceeded a count quota.
+ *
+ * Quotas are applied per userId-package-tag combination (UPTC). Tags can be null.
+ *
+ * Count and duration limits can be applied at the same time. Each limit is evaluated and
+ * controlled independently. If a UPTC reaches one of the limits, it will be considered out
+ * of quota until it is below that limit again. Limits are applied according to the category
+ * the UPTC is placed in. Categories are basic constructs to apply different limits to
+ * different groups of UPTCs. For example, standby buckets can be a set of categories, or
+ * foreground & background could be two categories. If every UPTC should have the limits
+ * applied, then only one category is needed.
+ *
+ * Note: all limits are enforced per category unless explicitly stated otherwise.
+ *
+ * @hide
+ */
+abstract class QuotaTracker {
+    private static final String TAG = QuotaTracker.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final String ALARM_TAG_QUOTA_CHECK = "*" + TAG + ".quota_check*";
+
+    @VisibleForTesting
+    static class Injector {
+        long getElapsedRealtime() {
+            return SystemClock.elapsedRealtime();
+        }
+
+        boolean isAlarmManagerReady() {
+            return LocalServices.getService(SystemServiceManager.class).isBootCompleted();
+        }
+    }
+
+    final Object mLock = new Object();
+    final Categorizer mCategorizer;
+    @GuardedBy("mLock")
+    private final ArraySet<QuotaChangeListener> mQuotaChangeListeners = new ArraySet<>();
+
+    /**
+     * Listener to track and manage when each package comes back within quota.
+     */
+    @GuardedBy("mLock")
+    private final InQuotaAlarmListener mInQuotaAlarmListener = new InQuotaAlarmListener();
+
+    /** "Free quota status" for apps. */
+    @GuardedBy("mLock")
+    private final SparseArrayMap<Boolean> mFreeQuota = new SparseArrayMap<>();
+
+    private final AlarmManager mAlarmManager;
+    protected final Context mContext;
+    protected final Injector mInjector;
+
+    @GuardedBy("mLock")
+    private boolean mIsQuotaFree;
+
+    /**
+     * If QuotaTracker should actively track events and check quota. If false, quota will be free
+     * and events will not be tracked.
+     */
+    @GuardedBy("mLock")
+    private boolean mIsEnabled = true;
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        private String getPackageName(Intent intent) {
+            final Uri uri = intent.getData();
+            return uri != null ? uri.getSchemeSpecificPart() : null;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent == null
+                    || intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                return;
+            }
+            final String action = intent.getAction();
+            if (action == null) {
+                Slog.e(TAG, "Received intent with null action");
+                return;
+            }
+            switch (action) {
+                case Intent.ACTION_PACKAGE_FULLY_REMOVED:
+                    final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                    synchronized (mLock) {
+                        onAppRemovedLocked(getPackageName(intent), uid);
+                    }
+                    break;
+                case Intent.ACTION_USER_REMOVED:
+                    final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+                    synchronized (mLock) {
+                        onUserRemovedLocked(userId);
+                    }
+                    break;
+            }
+        }
+    };
+
+    /** The maximum period any Category can have. */
+    @VisibleForTesting
+    static final long MAX_WINDOW_SIZE_MS = 30 * 24 * 60 * MINUTE_IN_MILLIS; // 1 month
+
+    /** The minimum time any window size can be. */
+    @VisibleForTesting
+    static final long MIN_WINDOW_SIZE_MS = 30_000; // 30 seconds
+
+    QuotaTracker(@NonNull Context context, @NonNull Categorizer categorizer,
+            @NonNull Injector injector) {
+        mCategorizer = categorizer;
+        mContext = context;
+        mInjector = injector;
+        mAlarmManager = mContext.getSystemService(AlarmManager.class);
+
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
+        filter.addDataScheme("package");
+        context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null,
+                BackgroundThread.getHandler());
+        final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
+        context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, userFilter, null,
+                BackgroundThread.getHandler());
+    }
+
+    // Exposed API to users.
+
+    /**
+     * @return true if the UPTC is within quota, false otherwise.
+     * @throws IllegalStateException if given categorizer returns a Category that's not recognized.
+     */
+    public boolean isWithinQuota(int userId, @NonNull String packageName, @Nullable String tag) {
+        synchronized (mLock) {
+            return isWithinQuotaLocked(userId, packageName, tag);
+        }
+    }
+
+    /**
+     * Indicates whether quota is currently free or not for a specific app. If quota is free, any
+     * currently ongoing events or instantaneous events won't be counted until quota is no longer
+     * free.
+     */
+    public void setQuotaFree(int userId, @NonNull String packageName, boolean isFree) {
+        synchronized (mLock) {
+            final boolean wasFree = mFreeQuota.getOrDefault(userId, packageName, Boolean.FALSE);
+            if (wasFree != isFree) {
+                mFreeQuota.add(userId, packageName, isFree);
+                onQuotaFreeChangedLocked(userId, packageName, isFree);
+            }
+        }
+    }
+
+    /** Indicates whether quota is currently free or not for all apps. */
+    public void setQuotaFree(boolean isFree) {
+        synchronized (mLock) {
+            if (mIsQuotaFree == isFree) {
+                return;
+            }
+            mIsQuotaFree = isFree;
+
+            if (!mIsEnabled) {
+                return;
+            }
+            onQuotaFreeChangedLocked(mIsQuotaFree);
+        }
+        scheduleQuotaCheck();
+    }
+
+    /**
+     * Register a {@link QuotaChangeListener} to be notified of when apps go in and out of quota.
+     */
+    public void registerQuotaChangeListener(QuotaChangeListener listener) {
+        synchronized (mLock) {
+            if (mQuotaChangeListeners.add(listener) && mQuotaChangeListeners.size() == 1) {
+                scheduleQuotaCheck();
+            }
+        }
+    }
+
+    /** Unregister the listener from future quota change notifications. */
+    public void unregisterQuotaChangeListener(QuotaChangeListener listener) {
+        synchronized (mLock) {
+            mQuotaChangeListeners.remove(listener);
+        }
+    }
+
+    // Configuration APIs
+
+    /**
+     * Completely enables or disables the quota tracker. If the tracker is disabled, all events and
+     * internal tracking data will be dropped.
+     */
+    public void setEnabled(boolean enable) {
+        synchronized (mLock) {
+            if (mIsEnabled == enable) {
+                return;
+            }
+            mIsEnabled = enable;
+
+            if (!mIsEnabled) {
+                mInQuotaAlarmListener.clearLocked();
+                mFreeQuota.clear();
+
+                dropEverythingLocked();
+            }
+        }
+    }
+
+    // Internal implementation.
+
+    @GuardedBy("mLock")
+    boolean isEnabledLocked() {
+        return mIsEnabled;
+    }
+
+    /** Returns true if global quota is free. */
+    @GuardedBy("mLock")
+    boolean isQuotaFreeLocked() {
+        return mIsQuotaFree;
+    }
+
+    /** Returns true if global quota is free or if quota is free for the given userId-package. */
+    @GuardedBy("mLock")
+    boolean isQuotaFreeLocked(int userId, @NonNull String packageName) {
+        return mIsQuotaFree || mFreeQuota.getOrDefault(userId, packageName, Boolean.FALSE);
+    }
+
+    /**
+     * Returns true only if quota is free for the given userId-package. Global quota is not taken
+     * into account.
+     */
+    @GuardedBy("mLock")
+    boolean isIndividualQuotaFreeLocked(int userId, @NonNull String packageName) {
+        return mFreeQuota.getOrDefault(userId, packageName, Boolean.FALSE);
+    }
+
+    /** The tracker has been disabled. Drop all events and internal tracking data. */
+    @GuardedBy("mLock")
+    abstract void dropEverythingLocked();
+
+    /** The global free quota status changed. */
+    @GuardedBy("mLock")
+    abstract void onQuotaFreeChangedLocked(boolean isFree);
+
+    /** The individual free quota status for the userId-package changed. */
+    @GuardedBy("mLock")
+    abstract void onQuotaFreeChangedLocked(int userId, @NonNull String packageName, boolean isFree);
+
+    /** Get the Handler used by the tracker. This Handler's thread will receive alarm callbacks. */
+    @NonNull
+    abstract Handler getHandler();
+
+    /** Makes sure to call out to AlarmManager on a separate thread. */
+    void scheduleAlarm(@AlarmManager.AlarmType int type, long triggerAtMillis, String tag,
+            AlarmManager.OnAlarmListener listener) {
+        // We don't know at what level in the lock hierarchy this tracker will be, so make sure to
+        // call out to AlarmManager without the lock held. The operation should be fast enough so
+        // put it on the FgThread.
+        FgThread.getHandler().post(() -> {
+            if (mInjector.isAlarmManagerReady()) {
+                mAlarmManager.set(type, triggerAtMillis, tag, listener, getHandler());
+            } else {
+                Slog.w(TAG, "Alarm not scheduled because boot isn't completed");
+            }
+        });
+    }
+
+    /** Makes sure to call out to AlarmManager on a separate thread. */
+    void cancelAlarm(AlarmManager.OnAlarmListener listener) {
+        // We don't know at what level in the lock hierarchy this tracker will be, so make sure to
+        // call out to AlarmManager without the lock held. The operation should be fast enough so
+        // put it on the FgThread.
+        FgThread.getHandler().post(() -> {
+            if (mInjector.isAlarmManagerReady()) {
+                mAlarmManager.cancel(listener);
+            } else {
+                Slog.w(TAG, "Alarm not cancelled because boot isn't completed");
+            }
+        });
+    }
+
+    /** Check the quota status of the specific UPTC. */
+    abstract void maybeUpdateQuotaStatus(int userId, @NonNull String packageName,
+            @Nullable String tag);
+
+    /** Check the quota status of all UPTCs in case a listener needs to be notified. */
+    @GuardedBy("mLock")
+    abstract void maybeUpdateAllQuotaStatusLocked();
+
+    /** Schedule a quota check for all apps. */
+    void scheduleQuotaCheck() {
+        // Using BackgroundThread because of the risk of lock contention.
+        BackgroundThread.getHandler().post(() -> {
+            synchronized (mLock) {
+                if (mQuotaChangeListeners.size() > 0) {
+                    maybeUpdateAllQuotaStatusLocked();
+                }
+            }
+        });
+    }
+
+    @GuardedBy("mLock")
+    abstract void handleRemovedAppLocked(String packageName, int uid);
+
+    @GuardedBy("mLock")
+    private void onAppRemovedLocked(String packageName, int uid) {
+        if (packageName == null) {
+            Slog.wtf(TAG, "Told app removed but given null package name.");
+            return;
+        }
+        final int userId = UserHandle.getUserId(uid);
+
+        mInQuotaAlarmListener.removeAlarmsLocked(userId, packageName);
+
+        mFreeQuota.delete(userId, packageName);
+
+        handleRemovedAppLocked(packageName, uid);
+    }
+
+    @GuardedBy("mLock")
+    abstract void handleRemovedUserLocked(int userId);
+
+    @GuardedBy("mLock")
+    private void onUserRemovedLocked(int userId) {
+        mInQuotaAlarmListener.removeAlarmsLocked(userId);
+        mFreeQuota.delete(userId);
+
+        handleRemovedUserLocked(userId);
+    }
+
+    @GuardedBy("mLock")
+    abstract boolean isWithinQuotaLocked(int userId, @NonNull String packageName,
+            @Nullable String tag);
+
+    void postQuotaStatusChanged(final int userId, @NonNull final String packageName,
+            @Nullable final String tag) {
+        BackgroundThread.getHandler().post(() -> {
+            final QuotaChangeListener[] listeners;
+            synchronized (mLock) {
+                // Only notify all listeners if we aren't directing to one listener.
+                listeners = mQuotaChangeListeners.toArray(
+                        new QuotaChangeListener[mQuotaChangeListeners.size()]);
+            }
+            for (QuotaChangeListener listener : listeners) {
+                listener.onQuotaStateChanged(userId, packageName, tag);
+            }
+        });
+    }
+
+    /**
+     * Return the time (in the elapsed realtime timebase) when the UPTC will have quota again. This
+     * value is only valid if the UPTC is currently out of quota.
+     */
+    @GuardedBy("mLock")
+    abstract long getInQuotaTimeElapsedLocked(int userId, @NonNull String packageName,
+            @Nullable String tag);
+
+    /**
+     * Maybe schedule a non-wakeup alarm for the next time this package will have quota to run
+     * again. This should only be called if the package is already out of quota.
+     */
+    @GuardedBy("mLock")
+    @VisibleForTesting
+    void maybeScheduleStartAlarmLocked(final int userId, @NonNull final String packageName,
+            @Nullable final String tag) {
+        if (mQuotaChangeListeners.size() == 0) {
+            // No need to schedule the alarm since we won't do anything when the app gets quota
+            // again.
+            return;
+        }
+
+        final String pkgString = string(userId, packageName, tag);
+
+        if (isWithinQuota(userId, packageName, tag)) {
+            // Already in quota. Why was this method called?
+            if (DEBUG) {
+                Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
+                        + " even though it's within quota");
+            }
+            mInQuotaAlarmListener.removeAlarmLocked(new Uptc(userId, packageName, tag));
+            maybeUpdateQuotaStatus(userId, packageName, tag);
+            return;
+        }
+
+        mInQuotaAlarmListener.addAlarmLocked(new Uptc(userId, packageName, tag),
+                getInQuotaTimeElapsedLocked(userId, packageName, tag));
+    }
+
+    @GuardedBy("mLock")
+    void cancelScheduledStartAlarmLocked(final int userId,
+            @NonNull final String packageName, @Nullable final String tag) {
+        mInQuotaAlarmListener.removeAlarmLocked(new Uptc(userId, packageName, tag));
+    }
+
+    static class AlarmQueue extends PriorityQueue<Pair<Uptc, Long>> {
+        AlarmQueue() {
+            super(1, (o1, o2) -> (int) (o1.second - o2.second));
+        }
+
+        /**
+         * Remove any instances of the Uptc from the queue.
+         *
+         * @return true if an instance was removed, false otherwise.
+         */
+        boolean remove(@NonNull Uptc uptc) {
+            boolean removed = false;
+            Pair[] alarms = toArray(new Pair[size()]);
+            for (int i = alarms.length - 1; i >= 0; --i) {
+                if (uptc.equals(alarms[i].first)) {
+                    remove(alarms[i]);
+                    removed = true;
+                }
+            }
+            return removed;
+        }
+    }
+
+    /** Track when UPTCs are expected to come back into quota. */
+    private class InQuotaAlarmListener implements AlarmManager.OnAlarmListener {
+        @GuardedBy("mLock")
+        private final AlarmQueue mAlarmQueue = new AlarmQueue();
+        /** The next time the alarm is set to go off, in the elapsed realtime timebase. */
+        @GuardedBy("mLock")
+        private long mTriggerTimeElapsed = 0;
+
+        @GuardedBy("mLock")
+        void addAlarmLocked(@NonNull Uptc uptc, long inQuotaTimeElapsed) {
+            mAlarmQueue.remove(uptc);
+            mAlarmQueue.offer(new Pair<>(uptc, inQuotaTimeElapsed));
+            setNextAlarmLocked();
+        }
+
+        @GuardedBy("mLock")
+        void clearLocked() {
+            cancelAlarm(this);
+            mAlarmQueue.clear();
+            mTriggerTimeElapsed = 0;
+        }
+
+        @GuardedBy("mLock")
+        void removeAlarmLocked(@NonNull Uptc uptc) {
+            if (mAlarmQueue.remove(uptc)) {
+                if (mAlarmQueue.size() == 0) {
+                    cancelAlarm(this);
+                } else {
+                    setNextAlarmLocked();
+                }
+            }
+        }
+
+        @GuardedBy("mLock")
+        void removeAlarmsLocked(int userId) {
+            boolean removed = false;
+            Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
+            for (int i = alarms.length - 1; i >= 0; --i) {
+                final Uptc uptc = (Uptc) alarms[i].first;
+                if (userId == uptc.userId) {
+                    mAlarmQueue.remove(alarms[i]);
+                    removed = true;
+                }
+            }
+            if (removed) {
+                setNextAlarmLocked();
+            }
+        }
+
+        @GuardedBy("mLock")
+        void removeAlarmsLocked(int userId, @NonNull String packageName) {
+            boolean removed = false;
+            Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
+            for (int i = alarms.length - 1; i >= 0; --i) {
+                final Uptc uptc = (Uptc) alarms[i].first;
+                if (userId == uptc.userId && packageName.equals(uptc.packageName)) {
+                    mAlarmQueue.remove(alarms[i]);
+                    removed = true;
+                }
+            }
+            if (removed) {
+                setNextAlarmLocked();
+            }
+        }
+
+        @GuardedBy("mLock")
+        private void setNextAlarmLocked() {
+            if (mAlarmQueue.size() > 0) {
+                final long nextTriggerTimeElapsed = mAlarmQueue.peek().second;
+                // Only schedule the alarm if one of the following is true:
+                // 1. There isn't one currently scheduled
+                // 2. The new alarm is significantly earlier than the previous alarm. If it's
+                // earlier but not significantly so, then we essentially delay the notification a
+                // few extra minutes.
+                if (mTriggerTimeElapsed == 0
+                        || nextTriggerTimeElapsed < mTriggerTimeElapsed - 3 * MINUTE_IN_MILLIS
+                        || mTriggerTimeElapsed < nextTriggerTimeElapsed) {
+                    // Use a non-wakeup alarm for this
+                    scheduleAlarm(AlarmManager.ELAPSED_REALTIME, nextTriggerTimeElapsed,
+                            ALARM_TAG_QUOTA_CHECK, this);
+                    mTriggerTimeElapsed = nextTriggerTimeElapsed;
+                }
+            } else {
+                mTriggerTimeElapsed = 0;
+            }
+        }
+
+        @Override
+        public void onAlarm() {
+            synchronized (mLock) {
+                while (mAlarmQueue.size() > 0) {
+                    final Pair<Uptc, Long> alarm = mAlarmQueue.peek();
+                    if (alarm.second <= mInjector.getElapsedRealtime()) {
+                        getHandler().post(() -> maybeUpdateQuotaStatus(
+                                alarm.first.userId, alarm.first.packageName, alarm.first.tag));
+                        mAlarmQueue.remove(alarm);
+                    } else {
+                        break;
+                    }
+                }
+                setNextAlarmLocked();
+            }
+        }
+
+        @GuardedBy("mLock")
+        void dumpLocked(IndentingPrintWriter pw) {
+            pw.println("In quota alarms:");
+            pw.increaseIndent();
+
+            if (mAlarmQueue.size() == 0) {
+                pw.println("NOT WAITING");
+            } else {
+                Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
+                for (int i = 0; i < alarms.length; ++i) {
+                    final Uptc uptc = (Uptc) alarms[i].first;
+                    pw.print(uptc);
+                    pw.print(": ");
+                    pw.print(alarms[i].second);
+                    pw.println();
+                }
+            }
+
+            pw.decreaseIndent();
+        }
+
+        @GuardedBy("mLock")
+        void dumpLocked(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+
+            proto.write(QuotaTrackerProto.InQuotaAlarmListener.TRIGGER_TIME_ELAPSED,
+                    mTriggerTimeElapsed);
+
+            Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
+            for (int i = 0; i < alarms.length; ++i) {
+                final long aToken = proto.start(QuotaTrackerProto.InQuotaAlarmListener.ALARMS);
+
+                final Uptc uptc = (Uptc) alarms[i].first;
+                uptc.dumpDebug(proto, QuotaTrackerProto.InQuotaAlarmListener.Alarm.UPTC);
+                proto.write(QuotaTrackerProto.InQuotaAlarmListener.Alarm.IN_QUOTA_TIME_ELAPSED,
+                        (Long) alarms[i].second);
+
+                proto.end(aToken);
+            }
+
+            proto.end(token);
+        }
+    }
+
+    //////////////////////////// DATA DUMP //////////////////////////////
+
+    /** Dump state in text format. */
+    public void dump(final IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            pw.println("Is enabled: " + mIsEnabled);
+            pw.println("Is global quota free: " + mIsQuotaFree);
+            pw.println("Current elapsed time: " + mInjector.getElapsedRealtime());
+            pw.println();
+
+            pw.println();
+            mInQuotaAlarmListener.dumpLocked(pw);
+
+            pw.println();
+            pw.println("Per-app free quota:");
+            pw.increaseIndent();
+            for (int u = 0; u < mFreeQuota.numMaps(); ++u) {
+                final int userId = mFreeQuota.keyAt(u);
+                for (int p = 0; p < mFreeQuota.numElementsForKey(userId); ++p) {
+                    final String pkgName = mFreeQuota.keyAt(u, p);
+
+                    pw.print(string(userId, pkgName, null));
+                    pw.print(": ");
+                    pw.println(mFreeQuota.get(userId, pkgName));
+                }
+            }
+            pw.decreaseIndent();
+        }
+    }
+
+    /**
+     * Dump state to proto.
+     *
+     * @param proto   The ProtoOutputStream to write to.
+     * @param fieldId The field ID of the {@link QuotaTrackerProto}.
+     */
+    public void dump(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+
+        synchronized (mLock) {
+            proto.write(QuotaTrackerProto.IS_ENABLED, mIsEnabled);
+            proto.write(QuotaTrackerProto.IS_GLOBAL_QUOTA_FREE, mIsQuotaFree);
+            proto.write(QuotaTrackerProto.ELAPSED_REALTIME, mInjector.getElapsedRealtime());
+            mInQuotaAlarmListener.dumpLocked(proto, QuotaTrackerProto.IN_QUOTA_ALARM_LISTENER);
+        }
+
+        proto.end(token);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 834e924..70fadb3 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3990,7 +3990,10 @@
 
         // If we are preparing an app transition, then delay changing
         // the visibility of this token until we execute that transition.
-        if (okToAnimate() && appTransition.isTransitionSet()) {
+        // Note that we ignore display frozen since we want the opening / closing transition type
+        // can be updated correctly even display frozen, and it's safe since in applyAnimation will
+        // still check DC#okToAnimate again if the transition animation is fine to apply.
+        if (okToAnimate(true /* ignoreFrozen */) && appTransition.isTransitionSet()) {
             if (visible) {
                 displayContent.mOpeningApps.add(this);
                 mEnteringAnimation = true;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 60f051c..331386f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -156,6 +156,8 @@
 import android.app.admin.DevicePolicyCache;
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.EnterPipRequestedItem;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
@@ -4959,6 +4961,44 @@
         }
     }
 
+    /**
+     * Requests that an activity should enter picture-in-picture mode if possible.
+     */
+    @Override
+    public void requestPictureInPictureMode(IBinder token) throws RemoteException {
+        mAmInternal.enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS,
+                "requestPictureInPictureMode");
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                final ActivityRecord activity = ActivityRecord.forTokenLocked(token);
+                if (activity == null) {
+                    return;
+                }
+
+                final boolean canEnterPictureInPicture = activity.checkEnterPictureInPictureState(
+                        "requestPictureInPictureMode", /* beforeStopping */ false);
+                if (!canEnterPictureInPicture) {
+                    throw new IllegalStateException(
+                            "Requested PIP on an activity that doesn't support it");
+                }
+
+                try {
+                    final ClientTransaction transaction = ClientTransaction.obtain(
+                            activity.app.getThread(),
+                            activity.token);
+                    transaction.addCallback(EnterPipRequestedItem.obtain());
+                    getLifecycleManager().scheduleTransaction(transaction);
+                } catch (Exception e) {
+                    Slog.w(TAG, "Failed to send enter pip requested item: "
+                            + activity.intent.getComponent(), e);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
     void dumpLastANRLocked(PrintWriter pw) {
         pw.println("ACTIVITY MANAGER LAST ANR (dumpsys activity lastanr)");
         if (mLastANRState == null) {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 6e09b94..369dde6 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -624,8 +624,8 @@
             // If we start the app transition at this point, we will interrupt it halfway with a
             // new rotation animation after the old one finally finishes. It's better to defer the
             // app transition.
-            if (screenRotationAnimation != null && screenRotationAnimation.isAnimating() &&
-                    mDisplayContent.getDisplayRotation().needsUpdate()) {
+            if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()
+                    && mDisplayContent.getDisplayRotation().needsUpdate()) {
                 ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                         "Delaying app transition for screen rotation animation to finish");
                 return false;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 5bf8e05..5a5c102 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -842,6 +842,7 @@
 
         // Update effect.
         w.mObscured = mTmpApplySurfaceChangesTransactionState.obscured;
+
         if (!mTmpApplySurfaceChangesTransactionState.obscured) {
             final boolean isDisplayed = w.isDisplayedLw();
 
@@ -872,6 +873,10 @@
                     mTmpApplySurfaceChangesTransactionState.preferredRefreshRate
                             = w.mAttrs.preferredRefreshRate;
                 }
+
+                mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing
+                        |= w.mAttrs.preferMinimalPostProcessing;
+
                 final int preferredModeId = getDisplayPolicy().getRefreshRatePolicy()
                         .getPreferredModeId(w);
                 if (mTmpApplySurfaceChangesTransactionState.preferredModeId == 0
@@ -3755,6 +3760,7 @@
                 mLastHasContent,
                 mTmpApplySurfaceChangesTransactionState.preferredRefreshRate,
                 mTmpApplySurfaceChangesTransactionState.preferredModeId,
+                mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing,
                 true /* inTraversal, must call performTraversalInTrans... below */);
 
         final boolean wallpaperVisible = mWallpaperController.isWallpaperVisible();
@@ -3988,15 +3994,23 @@
     }
 
     boolean okToDisplay() {
+        return okToDisplay(false);
+    }
+
+    boolean okToDisplay(boolean ignoreFrozen) {
         if (mDisplayId == DEFAULT_DISPLAY) {
-            return !mWmService.mDisplayFrozen
+            return (!mWmService.mDisplayFrozen || ignoreFrozen)
                     && mWmService.mDisplayEnabled && mWmService.mPolicy.isScreenOn();
         }
         return mDisplayInfo.state == Display.STATE_ON;
     }
 
     boolean okToAnimate() {
-        return okToDisplay() &&
+        return okToAnimate(false);
+    }
+
+    boolean okToAnimate(boolean ignoreFrozen) {
+        return okToDisplay(ignoreFrozen) &&
                 (mDisplayId != DEFAULT_DISPLAY || mWmService.mPolicy.okToAnimate());
     }
 
@@ -4058,6 +4072,7 @@
         boolean displayHasContent;
         boolean obscured;
         boolean syswin;
+        boolean preferMinimalPostProcessing;
         float preferredRefreshRate;
         int preferredModeId;
 
@@ -4065,6 +4080,7 @@
             displayHasContent = false;
             obscured = false;
             syswin = false;
+            preferMinimalPostProcessing = false;
             preferredRefreshRate = 0;
             preferredModeId = 0;
         }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index fbbc941..694a73d 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1878,7 +1878,7 @@
             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.getInsetsStateController()
+                insets = Insets.max(insets, mDisplayContent.getInsetsPolicy()
                         .getInsetsForDispatch(win).getSource(types.valueAt(i))
                         .calculateInsets(dfu, attrs.getFitIgnoreVisibility()));
             }
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 7dd9790..f778e4d 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -381,8 +381,8 @@
      * @return the {@link DisplayContent} or {@code null} if nothing is found.
      */
     DisplayContent getDisplayContent(String uniqueId) {
-        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
-            final DisplayContent display = mDisplayContents.get(i);
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final DisplayContent display = getChildAt(i);
             final boolean isValid = display.mDisplay.isValid();
             if (isValid && display.mDisplay.getUniqueId().equals(uniqueId)) {
                 return display;
@@ -394,8 +394,8 @@
 
     // TODO: Look into consolidating with getDisplayContentOrCreate()
     DisplayContent getDisplayContent(int displayId) {
-        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
-            final DisplayContent displayContent = mDisplayContents.get(i);
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final DisplayContent displayContent = getChildAt(i);
             if (displayContent.mDisplayId == displayId) {
                 return displayContent;
             }
@@ -438,16 +438,16 @@
 
     boolean startHomeOnAllDisplays(int userId, String reason) {
         boolean homeStarted = false;
-        for (int i = mDisplayContents.size() - 1; i >= 0; i--) {
-            final int displayId = mDisplayContents.get(i).mDisplayId;
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            final int displayId = getChildAt(i).mDisplayId;
             homeStarted |= startHomeOnDisplay(userId, reason, displayId);
         }
         return homeStarted;
     }
 
     void startHomeOnEmptyDisplays(String reason) {
-        for (int i = mDisplayContents.size() - 1; i >= 0; i--) {
-            final DisplayContent display = mDisplayContents.get(i);
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            final DisplayContent display = getChildAt(i);
             if (display.topRunningActivity() == null) {
                 startHomeOnDisplay(mCurrentUser, reason, display.mDisplayId);
             }
@@ -786,8 +786,8 @@
         final ArrayList<IBinder> topActivityTokens = new ArrayList<>();
         final ActivityStack topFocusedStack = getTopDisplayFocusedStack();
         // Traverse all displays.
-        for (int i = mDisplayContents.size() - 1; i >= 0; i--) {
-            final DisplayContent display = mDisplayContents.get(i);
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            final DisplayContent display = getChildAt(i);
             // Traverse all stacks on a display.
             for (int j = display.getStackCount() - 1; j >= 0; --j) {
                 final ActivityStack stack = display.getStackAt(j);
@@ -808,8 +808,8 @@
     }
 
     ActivityStack getTopDisplayFocusedStack() {
-        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
-            final ActivityStack focusedStack = mDisplayContents.get(i).getFocusedStack();
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final ActivityStack focusedStack = getChildAt(i).getFocusedStack();
             if (focusedStack != null) {
                 return focusedStack;
             }
@@ -828,8 +828,8 @@
         }
         // The top focused stack might not have a resumed activity yet - look on all displays in
         // focus order.
-        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
-            final DisplayContent display = mDisplayContents.get(i);
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final DisplayContent display = getChildAt(i);
             final ActivityRecord resumedActivityOnDisplay = display.getResumedActivity();
             if (resumedActivityOnDisplay != null) {
                 return resumedActivityOnDisplay;
@@ -858,8 +858,8 @@
         // previous app if this activity is being hosted by the process that is actually still the
         // foreground.
         WindowProcessController fgApp = null;
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = getChildAt(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 if (isTopDisplayFocusedStack(stack)) {
@@ -886,8 +886,8 @@
     boolean attachApplication(WindowProcessController app) throws RemoteException {
         final String processName = app.mName;
         boolean didSomething = false;
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = getChildAt(displayNdx);
             final ActivityStack stack = display.getFocusedStack();
             if (stack == null) {
                 continue;
@@ -955,8 +955,8 @@
         try {
             mStackSupervisor.getKeyguardController().beginActivityVisibilityUpdate();
             // First the front stacks. In case any are not fullscreen and are in front of home.
-            for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
-                final DisplayContent display = mDisplayContents.get(displayNdx);
+            for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+                final DisplayContent display = getChildAt(displayNdx);
                 display.ensureActivitiesVisible(starting, configChanges, preserveWindows,
                         notifyClients);
             }
@@ -985,8 +985,8 @@
         mCurrentUser = userId;
 
         mStackSupervisor.mStartingUsers.add(uss);
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = getChildAt(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 stack.switchUser(userId);
@@ -1165,8 +1165,8 @@
     }
 
     void executeAppTransitionForAllDisplay() {
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = getChildAt(displayNdx);
             display.mDisplayContent.executeAppTransition();
         }
     }
@@ -1199,8 +1199,8 @@
             }
         }
 
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = getChildAt(displayNdx);
             if (display.mDisplayId == preferredDisplayId) {
                 continue;
             }
@@ -1224,8 +1224,8 @@
     int finishTopCrashedActivities(WindowProcessController app, String reason) {
         Task finishedTask = null;
         ActivityStack focusedStack = getTopDisplayFocusedStack();
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = getChildAt(displayNdx);
             // It is possible that request to finish activity might also remove its task and stack,
             // so we need to be careful with indexes in the loop and check child count every time.
             for (int stackNdx = 0; stackNdx < display.getStackCount(); ++stackNdx) {
@@ -1256,9 +1256,9 @@
             result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
         }
 
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
             boolean resumedOnDisplay = false;
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+            final DisplayContent display = getChildAt(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 final ActivityRecord topRunningActivity = stack.topRunningActivity();
@@ -1299,9 +1299,9 @@
     }
 
     void applySleepTokens(boolean applyToStacks) {
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
             // Set the sleeping state of the display.
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+            final DisplayContent display = getChildAt(displayNdx);
             final boolean displayShouldSleep = display.shouldSleep();
             if (displayShouldSleep == display.isSleeping()) {
                 continue;
@@ -1355,8 +1355,8 @@
     }
 
     protected ActivityStack getStack(int stackId) {
-        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
-            final ActivityStack stack = mDisplayContents.get(i).getStack(stackId);
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final ActivityStack stack = getChildAt(i).getStack(stackId);
             if (stack != null) {
                 return stack;
             }
@@ -1366,9 +1366,8 @@
 
     /** @see DisplayContent#getStack(int, int) */
     ActivityStack getStack(int windowingMode, int activityType) {
-        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
-            final ActivityStack stack =
-                    mDisplayContents.get(i).getStack(windowingMode, activityType);
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final ActivityStack stack = getChildAt(i).getStack(windowingMode, activityType);
             if (stack != null) {
                 return stack;
             }
@@ -1452,8 +1451,8 @@
     ArrayList<ActivityManager.StackInfo> getAllStackInfos(int displayId) {
         ArrayList<ActivityManager.StackInfo> list = new ArrayList<>();
         if (displayId == INVALID_DISPLAY) {
-            for (int displayNdx = 0; displayNdx < mDisplayContents.size(); ++displayNdx) {
-                final DisplayContent display = mDisplayContents.get(displayNdx);
+            for (int displayNdx = 0; displayNdx < getChildCount(); ++displayNdx) {
+                final DisplayContent display = getChildAt(displayNdx);
                 for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                     final ActivityStack stack = display.getStackAt(stackNdx);
                     list.add(getStackInfo(stack));
@@ -1539,8 +1538,8 @@
     /** Update lists of UIDs that are present on displays and have access to them. */
     void updateUIDsPresentOnDisplay() {
         mDisplayAccessUIDs.clear();
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
-            final DisplayContent displayContent = mDisplayContents.get(displayNdx);
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent displayContent = getChildAt(displayNdx);
             // Only bother calculating the whitelist for private displays
             if (displayContent.isPrivate()) {
                 mDisplayAccessUIDs.append(
@@ -1635,8 +1634,8 @@
     }
 
     void prepareForShutdown() {
-        for (int i = 0; i < mDisplayContents.size(); i++) {
-            createSleepToken("shutdown", mDisplayContents.get(i).mDisplayId);
+        for (int i = 0; i < getChildCount(); i++) {
+            createSleepToken("shutdown", getChildAt(i).mDisplayId);
         }
     }
 
@@ -1712,8 +1711,8 @@
     }
 
     void scheduleDestroyAllActivities(WindowProcessController app, String reason) {
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = getChildAt(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 stack.scheduleDestroyActivities(app, reason);
@@ -1725,8 +1724,8 @@
     // successfully put to sleep.
     boolean putStacksToSleep(boolean allowDelay, boolean shuttingDown) {
         boolean allSleep = true;
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = getChildAt(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 // Stacks and activities could be removed while putting activities to sleep if
                 // the app process was gone. This prevents us getting exception by accessing an
@@ -1796,8 +1795,8 @@
     }
 
     boolean hasAwakeDisplay() {
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = getChildAt(displayNdx);
             if (!display.shouldSleep()) {
                 return true;
             }
@@ -2070,8 +2069,8 @@
         }
 
         // Now look through all displays
-        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
-            final DisplayContent display = mDisplayContents.get(i);
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final DisplayContent display = getChildAt(i);
             if (display == preferredDisplay) {
                 // We've already checked this one
                 continue;
@@ -2097,8 +2096,8 @@
      * @return Next valid {@link ActivityStack}, null if not found.
      */
     ActivityStack getNextValidLaunchStack(@NonNull ActivityRecord r, int currentFocus) {
-        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
-            final DisplayContent display = mDisplayContents.get(i);
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final DisplayContent display = getChildAt(i);
             if (display.mDisplayId == currentFocus) {
                 continue;
             }
@@ -2113,8 +2112,8 @@
 
     boolean handleAppDied(WindowProcessController app) {
         boolean hasVisibleActivities = false;
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = getChildAt(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 hasVisibleActivities |= stack.handleAppDiedLocked(app);
@@ -2226,8 +2225,8 @@
     }
 
     void finishVoiceTask(IVoiceInteractionSession session) {
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = getChildAt(displayNdx);
             final int numStacks = display.getStackCount();
             for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
@@ -2241,20 +2240,20 @@
      * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
      */
     void removeStacksInWindowingModes(int... windowingModes) {
-        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
-            mDisplayContents.get(i).removeStacksInWindowingModes(windowingModes);
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            getChildAt(i).removeStacksInWindowingModes(windowingModes);
         }
     }
 
     void removeStacksWithActivityTypes(int... activityTypes) {
-        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
-            mDisplayContents.get(i).removeStacksWithActivityTypes(activityTypes);
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            getChildAt(i).removeStacksWithActivityTypes(activityTypes);
         }
     }
 
     ActivityRecord topRunningActivity() {
-        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
-            final ActivityRecord topActivity = mDisplayContents.get(i).topRunningActivity();
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final ActivityRecord topActivity = getChildAt(i).topRunningActivity();
             if (topActivity != null) {
                 return topActivity;
             }
@@ -2263,9 +2262,9 @@
     }
 
     boolean allResumedActivitiesIdle() {
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
             // TODO(b/117135575): Check resumed activities on all visible stacks.
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+            final DisplayContent display = getChildAt(displayNdx);
             if (display.isSleeping()) {
                 // No resumed activities while display is sleeping.
                 continue;
@@ -2293,8 +2292,8 @@
 
     boolean allResumedActivitiesVisible() {
         boolean foundResumed = false;
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = getChildAt(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 final ActivityRecord r = stack.getResumedActivity();
@@ -2311,8 +2310,8 @@
 
     boolean allPausedActivitiesComplete() {
         boolean pausing = true;
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = getChildAt(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 final ActivityRecord r = stack.mPausingActivity;
@@ -2376,8 +2375,8 @@
     }
 
     void cancelInitializingActivities() {
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+            final DisplayContent display = getChildAt(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 stack.cancelInitializingActivities();
@@ -2462,9 +2461,9 @@
     }
 
     ActivityRecord isInAnyStack(IBinder token) {
-        int numDisplays = mDisplayContents.size();
+        int numDisplays = getChildCount();
         for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
-            final DisplayContent display = mDisplayContents.get(displayNdx);
+            final DisplayContent display = getChildAt(displayNdx);
             for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                 final ActivityStack stack = display.getStackAt(stackNdx);
                 final ActivityRecord r = stack.isInStackLocked(token);
@@ -2498,8 +2497,8 @@
             // activity on all displays, or if there are no resumed activities in the system.
             boolean noResumedActivities = true;
             boolean allFocusedProcessesDiffer = true;
-            for (int displayNdx = 0; displayNdx < mDisplayContents.size(); ++displayNdx) {
-                final DisplayContent displayContent = mDisplayContents.get(displayNdx);
+            for (int displayNdx = 0; displayNdx < getChildCount(); ++displayNdx) {
+                final DisplayContent displayContent = getChildAt(displayNdx);
                 final ActivityRecord resumedActivity = displayContent.getResumedActivity();
                 final WindowProcessController resumedActivityProcess =
                         resumedActivity == null ? null : resumedActivity.app;
@@ -2545,9 +2544,9 @@
             return getTopDisplayFocusedStack().getDumpActivitiesLocked(name);
         } else {
             ArrayList<ActivityRecord> activities = new ArrayList<>();
-            int numDisplays = mDisplayContents.size();
+            int numDisplays = getChildCount();
             for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
-                final DisplayContent display = mDisplayContents.get(displayNdx);
+                final DisplayContent display = getChildAt(displayNdx);
                 for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
                     final ActivityStack stack = display.getStackAt(stackNdx);
                     if (!dumpVisibleStacksOnly || stack.shouldBeVisible(null)) {
@@ -2562,8 +2561,8 @@
     public void dump(PrintWriter pw, String prefix) {
         pw.print(prefix);
         pw.println("topDisplayFocusedStack=" + getTopDisplayFocusedStack());
-        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
-            final DisplayContent display = mDisplayContents.get(i);
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final DisplayContent display = getChildAt(i);
             display.dump(pw, prefix, true /* dumpAll */);
         }
     }
@@ -2574,17 +2573,17 @@
      */
     void dumpDisplayConfigs(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.println("Display override configurations:");
-        final int displayCount = mDisplayContents.size();
+        final int displayCount = getChildCount();
         for (int i = 0; i < displayCount; i++) {
-            final DisplayContent displayContent = mDisplayContents.get(i);
+            final DisplayContent displayContent = getChildAt(i);
             pw.print(prefix); pw.print("  "); pw.print(displayContent.mDisplayId); pw.print(": ");
             pw.println(displayContent.getRequestedOverrideConfiguration());
         }
     }
 
     public void dumpDisplays(PrintWriter pw) {
-        for (int i = mDisplayContents.size() - 1; i >= 0; --i) {
-            final DisplayContent display = mDisplayContents.get(i);
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final DisplayContent display = getChildAt(i);
             pw.print("[id:" + display.mDisplayId + " stacks:");
             display.dumpStacks(pw);
             pw.print("]");
@@ -2595,13 +2594,12 @@
             String dumpPackage) {
         boolean printed = false;
         boolean needSep = false;
-        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
-            DisplayContent displayContent = mDisplayContents.get(displayNdx);
+        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
+            DisplayContent displayContent = getChildAt(displayNdx);
             pw.print("Display #"); pw.print(displayContent.mDisplayId);
             pw.println(" (activities from top to bottom):");
-            final DisplayContent display = mDisplayContents.get(displayNdx);
-            for (int stackNdx = display.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = display.getStackAt(stackNdx);
+            for (int stackNdx = displayContent.getStackCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = displayContent.getStackAt(stackNdx);
                 pw.println();
                 printed = stack.dump(fd, pw, dumpAll, dumpClient, dumpPackage, needSep);
                 needSep = printed;
@@ -2627,8 +2625,8 @@
             @WindowTraceLogLevel int logLevel) {
         final long token = proto.start(fieldId);
         super.dumpDebug(proto, CONFIGURATION_CONTAINER, logLevel);
-        for (int displayNdx = 0; displayNdx < mDisplayContents.size(); ++displayNdx) {
-            final DisplayContent displayContent = mDisplayContents.get(displayNdx);
+        for (int displayNdx = 0; displayNdx < getChildCount(); ++displayNdx) {
+            final DisplayContent displayContent = getChildAt(displayNdx);
             displayContent.dumpDebug(proto, DISPLAYS, logLevel);
         }
         mStackSupervisor.getKeyguardController().dumpDebug(proto, KEYGUARD_CONTROLLER);
diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java
index c38e63e..2d303fa 100644
--- a/services/core/java/com/android/server/wm/TaskPositioningController.java
+++ b/services/core/java/com/android/server/wm/TaskPositioningController.java
@@ -198,7 +198,9 @@
     }
 
     void finishTaskPositioning() {
-        mHandler.post(() -> {
+        // TaskPositioner attaches the InputEventReceiver to the animation thread. We need to
+        // dispose the receiver on the same thread to avoid race conditions.
+        mService.mAnimationHandler.post(() -> {
             if (DEBUG_TASK_POSITIONING) Slog.d(TAG_WM, "finishPositioning");
 
             synchronized (mService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index ce8e6dd..0c39b67 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2036,7 +2036,11 @@
     }
 
     boolean okToAnimate() {
-        return mDisplayContent != null && mDisplayContent.okToAnimate();
+        return okToAnimate(false /* ignoreFrozen */);
+    }
+
+    boolean okToAnimate(boolean ignoreFrozen) {
+        return mDisplayContent != null && mDisplayContent.okToAnimate(ignoreFrozen);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ba9e9ce..be41891 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1651,7 +1651,8 @@
                     outFrame, outContentInsets, outStableInsets, outDisplayCutout)) {
                 res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;
             }
-            outInsetsState.set(displayContent.getInsetsPolicy().getInsetsForDispatch(win));
+            outInsetsState.set(displayContent.getInsetsPolicy().getInsetsForDispatch(win),
+                    win.mClient instanceof IWindow.Stub /* copySource */);
 
             if (mInTouchMode) {
                 res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;
@@ -2335,7 +2336,8 @@
                     outStableInsets);
             outCutout.set(win.getWmDisplayCutout().getDisplayCutout());
             outBackdropFrame.set(win.getBackdropFrame(win.getFrameLw()));
-            outInsetsState.set(displayContent.getInsetsPolicy().getInsetsForDispatch(win));
+            outInsetsState.set(displayContent.getInsetsPolicy().getInsetsForDispatch(win),
+                    win.mClient instanceof IWindow.Stub /* copySource */);
             if (DEBUG) {
                 Slog.v(TAG_WM, "Relayout given client " + client.asBinder()
                         + ", requestedWidth=" + requestedWidth
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 3039d69..886f11a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -767,7 +767,7 @@
         mPowerManagerWrapper = powerManagerWrapper;
         mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
         mClientInsetsState =
-                getDisplayContent().getInsetsStateController().getInsetsForDispatch(this);
+                getDisplayContent().getInsetsPolicy().getInsetsForDispatch(this);
         if (DEBUG) {
             Slog.v(TAG, "Window " + this + " client=" + c.asBinder()
                             + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index eda69a9..1536816 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -84,6 +84,7 @@
 import static android.provider.Telephony.Carriers.DPC_URI;
 import static android.provider.Telephony.Carriers.ENFORCE_KEY;
 import static android.provider.Telephony.Carriers.ENFORCE_MANAGED_URI;
+import static android.security.keystore.AttestationUtils.USE_INDIVIDUAL_ATTESTATION;
 
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
@@ -5825,7 +5826,7 @@
         idTypeToAttestationFlag.put(ID_TYPE_IMEI, AttestationUtils.ID_TYPE_IMEI);
         idTypeToAttestationFlag.put(ID_TYPE_MEID, AttestationUtils.ID_TYPE_MEID);
         idTypeToAttestationFlag.put(
-                ID_TYPE_INDIVIDUAL_ATTESTATION, AttestationUtils.USE_INDIVIDUAL_ATTESTATION);
+                ID_TYPE_INDIVIDUAL_ATTESTATION, USE_INDIVIDUAL_ATTESTATION);
 
         int numFlagsSet = Integer.bitCount(idAttestationFlags);
         // No flags are set - return null to indicate no device ID attestation information should
@@ -5865,6 +5866,7 @@
 
         if (deviceIdAttestationRequired && attestationUtilsFlags.length > 0) {
             enforceCallerCanRequestDeviceIdAttestation(who, callerPackage, callingUid);
+            enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags);
         } else {
             enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
                     DELEGATION_CERT_INSTALL);
@@ -5959,6 +5961,17 @@
         return false;
     }
 
+    private void enforceIndividualAttestationSupportedIfRequested(int[] attestationUtilsFlags) {
+        for (int attestationFlag : attestationUtilsFlags) {
+            if (attestationFlag == USE_INDIVIDUAL_ATTESTATION
+                    && !mInjector.getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_DEVICE_UNIQUE_ATTESTATION)) {
+                throw new UnsupportedOperationException("Device Individual attestation is not "
+                        + "supported on this device.");
+            }
+        }
+    }
+
     @Override
     public boolean setKeyPairCertificate(ComponentName who, String callerPackage, String alias,
             byte[] cert, byte[] chain, boolean isUserSelectable) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 50ae376..cfe1318 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -146,6 +146,7 @@
 import com.android.server.restrictions.RestrictionsManagerService;
 import com.android.server.role.RoleManagerService;
 import com.android.server.rollback.RollbackManagerService;
+import com.android.server.security.FileIntegrityService;
 import com.android.server.security.KeyAttestationApplicationIdProviderService;
 import com.android.server.security.KeyChainSystemService;
 import com.android.server.signedconfig.SignedConfigService;
@@ -673,6 +674,13 @@
         AppCompatCallbacks.install(new long[0]);
         t.traceEnd();
 
+        // FileIntegrityService responds to requests from apps and the system. It needs to run after
+        // the source (i.e. keystore) is ready, and before the apps (or the first customer in the
+        // system) run.
+        t.traceBegin("StartFileIntegrityService");
+        mSystemServiceManager.startService(FileIntegrityService.class);
+        t.traceEnd();
+
         // Wait for installd to finish starting up so that it has a chance to
         // create critical directories such as /data/user with the appropriate
         // permissions.  We need this to complete before we initialize other services.
diff --git a/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/test.apk b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/test.apk
new file mode 100644
index 0000000..6345c98
--- /dev/null
+++ b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/test.apk
Binary files differ
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index 5c2ad94..73a191d 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -57,6 +57,7 @@
 
     @Mock private AudioService mMockAudioService;
     @Spy private AudioDeviceInventory mSpyDevInventory;
+    @Spy private AudioSystemAdapter mSpyAudioSystem;
 
     private BluetoothDevice mFakeBtDevice;
 
@@ -65,7 +66,8 @@
         mContext = InstrumentationRegistry.getTargetContext();
 
         mMockAudioService = mock(AudioService.class);
-        mSpyDevInventory = spy(new AudioDeviceInventory());
+        mSpyAudioSystem = spy(AudioSystemAdapter.getAlwaysOkAdapter());
+        mSpyDevInventory = spy(new AudioDeviceInventory(mSpyAudioSystem));
         mAudioDeviceBroker = new AudioDeviceBroker(mContext, mMockAudioService, mSpyDevInventory);
         mSpyDevInventory.setDeviceBroker(mAudioDeviceBroker);
 
@@ -81,8 +83,9 @@
     public void testSetUpAndTearDown() { }
 
     /**
-     * Verify call to postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() for connection
-     * calls into AudioDeviceInventory with the right params
+     * postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent() for connection:
+     * - verify it calls into AudioDeviceInventory with the right params
+     * - verify it calls into AudioSystem and stays connected (no 2nd call to disconnect)
      * @throws Exception
      */
     @Test
@@ -92,7 +95,7 @@
 
         mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
                 BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1);
-        Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
+        Thread.sleep(2 * MAX_MESSAGE_HANDLING_DELAY_MS);
         verify(mSpyDevInventory, times(1)).setBluetoothA2dpDeviceConnectionState(
                 any(BluetoothDevice.class),
                 ArgumentMatchers.eq(BluetoothProfile.STATE_CONNECTED) /*state*/,
@@ -100,6 +103,14 @@
                 ArgumentMatchers.eq(true) /*suppressNoisyIntent*/, anyInt() /*musicDevice*/,
                 ArgumentMatchers.eq(1) /*a2dpVolume*/
         );
+
+        final String expectedName = mFakeBtDevice.getName() == null ? "" : mFakeBtDevice.getName();
+        verify(mSpyAudioSystem, times(1)).setDeviceConnectionState(
+                ArgumentMatchers.eq(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP),
+                ArgumentMatchers.eq(AudioSystem.DEVICE_STATE_AVAILABLE),
+                ArgumentMatchers.eq(mFakeBtDevice.getAddress()),
+                ArgumentMatchers.eq(expectedName),
+                anyInt() /*codec*/);
     }
 
     /**
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 c080332..a2376a6 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -16,41 +16,380 @@
 
 package com.android.server.integrity;
 
+import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
+import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
+import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
+import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
+import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE;
+import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_UID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.internal.verification.VerificationModeFactory.times;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.integrity.AppInstallMetadata;
+import android.content.integrity.AtomicFormula;
+import android.content.integrity.Rule;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.ParceledListSlice;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.R;
 import com.android.server.LocalServices;
+import com.android.server.integrity.engine.RuleEvaluationEngine;
+import com.android.server.integrity.model.IntegrityCheckResult;
+import com.android.server.testutils.TestUtils;
 
+import org.junit.After;
 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 java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 /** Unit test for {@link com.android.server.integrity.AppIntegrityManagerServiceImpl} */
 @RunWith(AndroidJUnit4.class)
 public class AppIntegrityManagerServiceImplTest {
+    private static final String TEST_DIR = "AppIntegrityManagerServiceImplTest";
 
-    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+    private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
+    private static final String VERSION = "version";
+    private static final String TEST_FRAMEWORK_PACKAGE = "com.android.frameworks.servicestests";
+
+    private static final String PACKAGE_NAME = "com.test.app";
+    private static final int VERSION_CODE = 100;
+    private static final String INSTALLER = TEST_FRAMEWORK_PACKAGE;
+    // These are obtained by running the test and checking logcat.
+    private static final String APP_CERT =
+            "949ADC6CB92FF09E3784D6E9504F26F9BEAC06E60D881D55A6A81160F9CD6FD1";
+    private static final String INSTALLER_CERT =
+            "301AA3CB081134501C45F1422ABC66C24224FD5DED5FDC8F17E697176FD866AA";
+    // We use SHA256 for package names longer than 32 characters.
+    private static final String INSTALLER_SHA256 =
+            "786933C28839603EB48C50B2A688DC6BE52C833627CB2731FF8466A2AE9F94CD";
+
+    @org.junit.Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     @Mock PackageManagerInternal mPackageManagerInternal;
+    @Mock Context mMockContext;
+    @Mock Resources mMockResources;
+    @Mock RuleEvaluationEngine mRuleEvaluationEngine;
+    @Mock IntegrityFileManager mIntegrityFileManager;
+    @Mock Handler mHandler;
 
+    private PackageManager mSpyPackageManager;
+    private File mTestApk;
+
+    private final Context mRealContext = InstrumentationRegistry.getTargetContext();
     // under test
     private AppIntegrityManagerServiceImpl mService;
 
     @Before
-    public void setup() {
-        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
+    public void setup() throws Exception {
+        mTestApk = File.createTempFile("TestApk", /* suffix= */ null);
+        mTestApk.deleteOnExit();
+        try (InputStream inputStream = mRealContext.getAssets().open(TEST_DIR + "/test.apk")) {
+            Files.copy(inputStream, mTestApk.toPath(), StandardCopyOption.REPLACE_EXISTING);
+        }
 
-        mService = new AppIntegrityManagerServiceImpl(InstrumentationRegistry.getContext());
+        mService =
+                new AppIntegrityManagerServiceImpl(
+                        mMockContext,
+                        mPackageManagerInternal,
+                        mRuleEvaluationEngine,
+                        mIntegrityFileManager,
+                        mHandler);
+
+        mSpyPackageManager = spy(mRealContext.getPackageManager());
+        // setup mocks to prevent NPE
+        when(mMockContext.getPackageManager()).thenReturn(mSpyPackageManager);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockResources.getStringArray(anyInt())).thenReturn(new String[] {});
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mTestApk.delete();
+    }
+
+    // This is not a test of the class, but more of a safeguard that we don't block any install in
+    // the default case. This is needed because we don't have any emergency kill switch to disable
+    // this component.
+    @Test
+    public void default_allow() throws Exception {
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
+        mService = AppIntegrityManagerServiceImpl.create(mMockContext);
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mMockContext, times(2))
+                .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
+        Intent intent = makeVerificationIntent();
+
+        broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
+
+        // Since we are not mocking handler in this case, we must wait.
+        // 2 seconds should be a sensible timeout.
+        Thread.sleep(2000);
+        verify(mPackageManagerInternal)
+                .setIntegrityVerificationResult(
+                        1, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
     }
 
     @Test
-    public void noop() {
-        // We need this test just as a place holder since an empty test suite is treated as error.
+    public void updateRuleSet_notAuthorized() throws Exception {
+        makeUsSystemApp();
+        Rule rule =
+                new Rule(
+                        new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
+                        Rule.DENY);
+        TestUtils.assertExpectException(
+                SecurityException.class,
+                "Only system packages specified in config_integrityRuleProviderPackages are"
+                        + " allowed to call this method.",
+                () ->
+                        mService.updateRuleSet(
+                                VERSION,
+                                new ParceledListSlice<>(Arrays.asList(rule)),
+                                /* statusReceiver= */ null));
+    }
+
+    @Test
+    public void updateRuleSet_notSystemApp() throws Exception {
+        whitelistUsAsRuleProvider();
+        Rule rule =
+                new Rule(
+                        new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
+                        Rule.DENY);
+        TestUtils.assertExpectException(
+                SecurityException.class,
+                "Only system packages specified in config_integrityRuleProviderPackages are"
+                        + " allowed to call this method.",
+                () ->
+                        mService.updateRuleSet(
+                                VERSION,
+                                new ParceledListSlice<>(Arrays.asList(rule)),
+                                /* statusReceiver= */ null));
+    }
+
+    @Test
+    public void updateRuleSet_authorized() throws Exception {
+        whitelistUsAsRuleProvider();
+        makeUsSystemApp();
+        Rule rule =
+                new Rule(
+                        new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
+                        Rule.DENY);
+
+        // no SecurityException
+        mService.updateRuleSet(
+                VERSION, new ParceledListSlice<>(Arrays.asList(rule)), mock(IntentSender.class));
+    }
+
+    @Test
+    public void updateRuleSet_correctMethodCall() throws Exception {
+        whitelistUsAsRuleProvider();
+        makeUsSystemApp();
+        IntentSender mockReceiver = mock(IntentSender.class);
+        List<Rule> rules =
+                Arrays.asList(
+                        new Rule(
+                                new AtomicFormula.StringAtomicFormula(
+                                        AtomicFormula.PACKAGE_NAME,
+                                        PACKAGE_NAME,
+                                        /* isHashedValue= */ false),
+                                Rule.DENY));
+
+        mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver);
+        runJobInHandler();
+
+        verify(mIntegrityFileManager).writeRules(VERSION, TEST_FRAMEWORK_PACKAGE, rules);
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mockReceiver).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any());
+        assertEquals(STATUS_SUCCESS, intentCaptor.getValue().getIntExtra(EXTRA_STATUS, -1));
+    }
+
+    @Test
+    public void updateRuleSet_fail() throws Exception {
+        whitelistUsAsRuleProvider();
+        makeUsSystemApp();
+        doThrow(new IOException()).when(mIntegrityFileManager).writeRules(any(), any(), any());
+        IntentSender mockReceiver = mock(IntentSender.class);
+        List<Rule> rules =
+                Arrays.asList(
+                        new Rule(
+                                new AtomicFormula.StringAtomicFormula(
+                                        AtomicFormula.PACKAGE_NAME,
+                                        PACKAGE_NAME,
+                                        /* isHashedValue= */ false),
+                                Rule.DENY));
+
+        mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver);
+        runJobInHandler();
+
+        verify(mIntegrityFileManager).writeRules(VERSION, TEST_FRAMEWORK_PACKAGE, rules);
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mockReceiver).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any());
+        assertEquals(STATUS_FAILURE, intentCaptor.getValue().getIntExtra(EXTRA_STATUS, -1));
+    }
+
+    @Test
+    public void broadcastReceiverRegistration() throws Exception {
+        ArgumentCaptor<IntentFilter> intentFilterCaptor =
+                ArgumentCaptor.forClass(IntentFilter.class);
+
+        verify(mMockContext).registerReceiver(any(), intentFilterCaptor.capture(), any(), any());
+        assertEquals(1, intentFilterCaptor.getValue().countActions());
+        assertEquals(
+                Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION,
+                intentFilterCaptor.getValue().getAction(0));
+        assertEquals(1, intentFilterCaptor.getValue().countDataTypes());
+        assertEquals(PACKAGE_MIME_TYPE, intentFilterCaptor.getValue().getDataType(0));
+    }
+
+    @Test
+    public void handleBroadcast_correctArgs() throws Exception {
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mMockContext)
+                .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
+        Intent intent = makeVerificationIntent();
+        when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
+
+        broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
+        runJobInHandler();
+
+        ArgumentCaptor<AppInstallMetadata> metadataCaptor =
+                ArgumentCaptor.forClass(AppInstallMetadata.class);
+        Map<String, String> allowedInstallers = new HashMap<>();
+        ArgumentCaptor<Map<String, String>> allowedInstallersCaptor =
+                ArgumentCaptor.forClass(allowedInstallers.getClass());
+        verify(mRuleEvaluationEngine)
+                .evaluate(metadataCaptor.capture(), allowedInstallersCaptor.capture());
+        AppInstallMetadata appInstallMetadata = metadataCaptor.getValue();
+        allowedInstallers = allowedInstallersCaptor.getValue();
+        assertEquals(PACKAGE_NAME, appInstallMetadata.getPackageName());
+        assertEquals(APP_CERT, appInstallMetadata.getAppCertificate());
+        assertEquals(INSTALLER_SHA256, appInstallMetadata.getInstallerName());
+        assertEquals(INSTALLER_CERT, appInstallMetadata.getInstallerCertificate());
+        assertEquals(VERSION_CODE, appInstallMetadata.getVersionCode());
+        assertFalse(appInstallMetadata.isPreInstalled());
+        // These are hardcoded in the test apk
+        assertEquals(2, allowedInstallers.size());
+        assertEquals("cert_1", allowedInstallers.get("store_1"));
+        assertEquals("cert_2", allowedInstallers.get("store_2"));
+    }
+
+    @Test
+    public void handleBroadcast_allow() throws Exception {
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mMockContext)
+                .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
+        Intent intent = makeVerificationIntent();
+        when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
+
+        broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
+        runJobInHandler();
+
+        verify(mPackageManagerInternal)
+                .setIntegrityVerificationResult(
+                        1, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
+    }
+
+    @Test
+    public void handleBroadcast_reject() throws Exception {
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mMockContext)
+                .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
+        when(mRuleEvaluationEngine.evaluate(any(), any()))
+                .thenReturn(
+                        IntegrityCheckResult.deny(
+                                new Rule(
+                                        new AtomicFormula.BooleanAtomicFormula(
+                                                AtomicFormula.PRE_INSTALLED, false),
+                                        Rule.DENY)));
+        Intent intent = makeVerificationIntent();
+
+        broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
+        runJobInHandler();
+
+        verify(mPackageManagerInternal)
+                .setIntegrityVerificationResult(
+                        1, PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
+    }
+
+    private void whitelistUsAsRuleProvider() {
+        Resources mockResources = mock(Resources.class);
+        when(mockResources.getStringArray(R.array.config_integrityRuleProviderPackages))
+                .thenReturn(new String[] {TEST_FRAMEWORK_PACKAGE});
+        when(mMockContext.getResources()).thenReturn(mockResources);
+    }
+
+    private void runJobInHandler() {
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        // sendMessageAtTime is the first non-final method in the call chain when "post" is invoked.
+        verify(mHandler).sendMessageAtTime(messageCaptor.capture(), anyLong());
+        messageCaptor.getValue().getCallback().run();
+    }
+
+    private void makeUsSystemApp() throws Exception {
+        PackageInfo packageInfo =
+                mRealContext.getPackageManager().getPackageInfo(TEST_FRAMEWORK_PACKAGE, 0);
+        packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        doReturn(packageInfo)
+                .when(mSpyPackageManager)
+                .getPackageInfo(eq(TEST_FRAMEWORK_PACKAGE), anyInt());
+    }
+
+    private Intent makeVerificationIntent() 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_UID,
+                mRealContext.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/IntegrityUtilsTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityUtilsTest.java
new file mode 100644
index 0000000..ac7f8f9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityUtilsTest.java
@@ -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.server.integrity;
+
+import static com.android.server.integrity.IntegrityUtils.getBytesFromHexDigest;
+import static com.android.server.integrity.IntegrityUtils.getHexDigest;
+import static com.android.server.testutils.TestUtils.assertExpectException;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit test for {@link com.android.server.integrity.IntegrityUtils} */
+@RunWith(AndroidJUnit4.class)
+public class IntegrityUtilsTest {
+
+    private static final String HEX_DIGEST = "1234567890ABCDEF";
+    private static final byte[] BYTES =
+            new byte[] {0x12, 0x34, 0x56, 0x78, (byte) 0x90, (byte) 0xAB, (byte) 0xCD, (byte) 0xEF};
+
+    @Test
+    public void testGetBytesFromHexDigest() {
+        assertArrayEquals(BYTES, getBytesFromHexDigest(HEX_DIGEST));
+    }
+
+    @Test
+    public void testGetHexDigest() {
+        assertEquals(HEX_DIGEST, getHexDigest(BYTES));
+    }
+
+    @Test
+    public void testInvalidHexDigest() {
+        assertExpectException(
+                IllegalArgumentException.class,
+                "must have even length",
+                () -> getBytesFromHexDigest("ABC"));
+
+        assertExpectException(
+                IllegalArgumentException.class,
+                "Invalid hex char",
+                () -> getBytesFromHexDigest("GH"));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index d6ef2d4..7457067 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -101,30 +101,6 @@
         return mService.getLong(SYNTHETIC_PASSWORD_HANDLE_KEY, 0, userId) != 0;
     }
 
-    @Test
-    public void testPasswordMigration() throws RemoteException {
-        final LockscreenCredential password = newPassword("testPasswordMigration-password");
-
-        disableSyntheticPassword();
-        assertTrue(mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID));
-        long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
-        final byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
-        enableSyntheticPassword();
-        // Performs migration
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                password, 0, PRIMARY_USER_ID)
-                    .getResponseCode());
-        assertEquals(sid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
-        assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
-
-        // SP-based verification
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                password, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
-        assertArrayNotEquals(primaryStorageKey,
-                mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
-    }
-
     protected void initializeCredentialUnderSP(LockscreenCredential password, int userId)
             throws RemoteException {
         enableSyntheticPassword();
@@ -253,81 +229,6 @@
     }
 
     @Test
-    public void testManagedProfileUnifiedChallengeMigration() throws RemoteException {
-        LockscreenCredential UnifiedPassword = newPassword("unified-pwd");
-        disableSyntheticPassword();
-        mService.setLockCredential(UnifiedPassword, nonePassword(), PRIMARY_USER_ID);
-        mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
-        final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
-        final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
-        byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
-        byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
-        assertTrue(primarySid != 0);
-        assertTrue(profileSid != 0);
-        assertTrue(profileSid != primarySid);
-
-        // do migration
-        enableSyntheticPassword();
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                UnifiedPassword, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
-
-        // verify
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                UnifiedPassword, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
-        assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
-        assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
-        assertArrayNotEquals(primaryStorageKey,
-                mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
-        assertArrayNotEquals(profileStorageKey,
-                mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID));
-        assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
-        assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID));
-    }
-
-    @Test
-    public void testManagedProfileSeparateChallengeMigration() throws RemoteException {
-        LockscreenCredential primaryPassword = newPassword("primary");
-        LockscreenCredential profilePassword = newPassword("profile");
-        disableSyntheticPassword();
-        mService.setLockCredential(primaryPassword, nonePassword(), PRIMARY_USER_ID);
-        mService.setLockCredential(profilePassword, nonePassword(), MANAGED_PROFILE_USER_ID);
-        final long primarySid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
-        final long profileSid = mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID);
-        byte[] primaryStorageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
-        byte[] profileStorageKey = mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID);
-        assertTrue(primarySid != 0);
-        assertTrue(profileSid != 0);
-        assertTrue(profileSid != primarySid);
-
-        // do migration
-        enableSyntheticPassword();
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                primaryPassword, 0, PRIMARY_USER_ID)
-                        .getResponseCode());
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                profilePassword, 0, MANAGED_PROFILE_USER_ID)
-                .getResponseCode());
-
-        // verify
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                primaryPassword, 0, PRIMARY_USER_ID)
-                .getResponseCode());
-        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
-                profilePassword, 0, MANAGED_PROFILE_USER_ID)
-                .getResponseCode());
-        assertEquals(primarySid, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
-        assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
-        assertArrayNotEquals(primaryStorageKey,
-                mStorageManager.getUserUnlockToken(PRIMARY_USER_ID));
-        assertArrayNotEquals(profileStorageKey,
-                mStorageManager.getUserUnlockToken(MANAGED_PROFILE_USER_ID));
-        assertTrue(hasSyntheticPassword(PRIMARY_USER_ID));
-        assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID));
-    }
-
-    @Test
     public void testTokenBasedResetPassword() throws RemoteException {
         LockscreenCredential password = newPassword("password");
         LockscreenCredential pattern = newPattern("123654");
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 81fb0ec..13643a0 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -62,6 +62,7 @@
 import android.os.PowerSaveState;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.platform.test.annotations.FlakyTest;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
 import android.view.Display;
@@ -734,6 +735,7 @@
         assertThat(mService.getWakefulness()).isEqualTo(WAKEFULNESS_ASLEEP);
     }
 
+    @FlakyTest
     @Test
     public void testInattentiveSleep_goesToSleepWithWakeLock() throws Exception {
         final String pkg = mContextSpy.getOpPackageName();
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
index 89c7dd4..ecdc58e 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
@@ -29,6 +29,7 @@
 import android.content.Context;
 import android.os.Handler;
 import android.os.Looper;
+import android.platform.test.annotations.FlakyTest;
 import android.util.ArrayMap;
 
 import androidx.test.InstrumentationRegistry;
@@ -328,6 +329,7 @@
         veriryWtf(0);
     }
 
+    @FlakyTest
     @Test
     public void testAll() throws Exception {
         // Run multiple tests on the single target instance.
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 f1de6e9..438de78 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -20,20 +20,30 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.PictureInPictureParams;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.EnterPipRequestedItem;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.view.IDisplayWindowListener;
 import android.view.WindowContainerTransaction;
 
@@ -42,6 +52,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.MockitoSession;
 
 import java.util.ArrayList;
@@ -56,6 +67,9 @@
 @RunWith(WindowTestRunner.class)
 public class ActivityTaskManagerServiceTests extends ActivityTestsBase {
 
+    private final ArgumentCaptor<ClientTransaction> mClientTransactionCaptor =
+            ArgumentCaptor.forClass(ClientTransaction.class);
+
     @Before
     public void setUp() throws Exception {
         doReturn(false).when(mService).isBooting();
@@ -78,6 +92,39 @@
     }
 
     @Test
+    public void testOnPictureInPictureRequested() throws RemoteException {
+        final ActivityStack stack = new StackBuilder(mRootActivityContainer).build();
+        final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
+        ClientLifecycleManager lifecycleManager = mService.getLifecycleManager();
+        doNothing().when(lifecycleManager).scheduleTransaction(any());
+        doReturn(true).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean());
+
+        mService.requestPictureInPictureMode(activity.token);
+
+        verify(lifecycleManager).scheduleTransaction(mClientTransactionCaptor.capture());
+        final ClientTransaction transaction = mClientTransactionCaptor.getValue();
+        // Check that only an enter pip request item callback was scheduled.
+        assertEquals(1, transaction.getCallbacks().size());
+        assertTrue(transaction.getCallbacks().get(0) instanceof EnterPipRequestedItem);
+        // Check the activity lifecycle state remains unchanged.
+        assertNull(transaction.getLifecycleStateRequest());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testOnPictureInPictureRequested_cannotEnterPip() throws RemoteException {
+        final ActivityStack stack = new StackBuilder(mRootActivityContainer).build();
+        final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
+        ClientLifecycleManager lifecycleManager = mService.getLifecycleManager();
+        doNothing().when(lifecycleManager).scheduleTransaction(any());
+        doReturn(false).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean());
+
+        mService.requestPictureInPictureMode(activity.token);
+
+        // Check enter no transactions with enter pip requests are made.
+        verify(lifecycleManager, times(0)).scheduleTransaction(any());
+    }
+
+    @Test
     public void testTaskTransaction() {
         removeGlobalMinSizeRestriction();
         final ActivityStack stack = new StackBuilder(mRootActivityContainer)
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 ccbafd4..612e051 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -514,6 +514,13 @@
         // Prevent mInitialDisplayCutout from being updated from real display (e.g. null
         // if the device has no cutout).
         final DisplayContent dc = createDisplayNoUpdateDisplayInfo();
+        // This test assumes it's a top cutout on a portrait display, so if it happens to be a
+        // landscape display let's rotate it.
+        if (dc.mInitialDisplayHeight < dc.mInitialDisplayWidth) {
+            int tmp = dc.mInitialDisplayHeight;
+            dc.mInitialDisplayHeight = dc.mInitialDisplayWidth;
+            dc.mInitialDisplayWidth = tmp;
+        }
         // Rotation may use real display info to compute bound, so here also uses the
         // same width and height.
         final int displayWidth = dc.mInitialDisplayWidth;
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 de73645..5aece45 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -272,7 +272,7 @@
         assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
 
         final InsetsState state =
-                mDisplayContent.getInsetsStateController().getInsetsForDispatch(mWindow);
+                mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow);
         state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
         state.getSource(InsetsState.ITYPE_NAVIGATION_BAR).setVisible(false);
         mWindow.mAttrs.setFitIgnoreVisibility(true);
@@ -294,7 +294,7 @@
         assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
 
         final InsetsState state =
-                mDisplayContent.getInsetsStateController().getInsetsForDispatch(mWindow);
+                mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow);
         state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
         state.getSource(InsetsState.ITYPE_NAVIGATION_BAR).setVisible(false);
         mWindow.mAttrs.setFitIgnoreVisibility(false);
@@ -452,7 +452,7 @@
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN;
-        mDisplayContent.getInsetsStateController().getInsetsForDispatch(mWindow)
+        mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow)
                 .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
         addWindow(mWindow);
 
@@ -473,7 +473,7 @@
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN;
-        mDisplayContent.getInsetsStateController().getInsetsForDispatch(mWindow)
+        mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow)
                 .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
         mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         addWindow(mWindow);
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index fbb1380..80a55b2 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -344,7 +344,7 @@
             DevicePolicyManager devicePolicyManager =
                     (DevicePolicyManager) context.getSystemService(
                             Context.DEVICE_POLICY_SERVICE);
-            if (devicePolicyManager != null && devicePolicyManager.checkDeviceIdentifierAccess(
+            if (devicePolicyManager != null && devicePolicyManager.hasDeviceIdentifierAccess(
                     callingPackage, pid, uid)) {
                 return true;
             }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 4bb237f..eea08dc 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2316,13 +2316,41 @@
     /**
      * Determine whether to use only RSRP for the number of LTE signal bars.
      * @hide
+     *
+     * @deprecated use {@link #KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT}.
      */
     // FIXME: this key and related keys must not be exposed without a consistent philosophy for
     // all RATs.
+    @Deprecated
     public static final String KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL =
             "use_only_rsrp_for_lte_signal_bar_bool";
 
     /**
+     * Bit-field integer to determine whether to use Reference Signal Received Power (RSRP),
+     * Reference Signal Received Quality (RSRQ), or/and Reference Signal Signal to Noise Ratio
+     * (RSSNR) for the number of LTE signal bars and signal criteria reporting enabling.
+     *
+     * <p> If a measure is not set, signal criteria reporting from modem will not be triggered and
+     * not be used for calculating signal level. If multiple measures are set bit, the parameter
+     * whose value is smallest is used to indicate the signal level.
+     *
+     *  RSRP = 1 << 0,
+     *  RSRQ = 1 << 1,
+     *  RSSNR = 1 << 2,
+     *
+     *  The value of this key must be bitwise OR of {@link CellSignalStrengthLte#USE_RSRP},
+     *  {@link CellSignalStrengthLte#USE_RSRQ}, {@link CellSignalStrengthLte#USE_RSSNR}.
+     *
+     * For example, if both RSRP and RSRQ are used, the value of key is 3 (1 << 0 | 1 << 1).
+     * If the key is invalid or not configured, a default value (RSRP | RSSNR = 1 << 0 | 1 << 2)
+     * will apply.
+     *
+     * @hide
+     */
+    public static final String KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT =
+            "parameters_used_for_lte_signal_bar_int";
+
+    /**
      * List of 4 customized 5G SS reference signal received power (SSRSRP) thresholds.
      *
      * Reference: 3GPP TS 38.215
@@ -2625,6 +2653,42 @@
             "lte_rsrp_thresholds_int_array";
 
     /**
+     * A list of 4 customized LTE Reference Signal Received Quality (RSRQ) thresholds.
+     *
+     * Reference: TS 136.133 v12.6.0 section 9.1.7 - RSRQ Measurement Report Mapping.
+     *
+     * 4 threshold integers must be within the boundaries [-34 dB, 3 dB], and the levels are:
+     *     "NONE: [-34, threshold1)"
+     *     "POOR: [threshold1, threshold2)"
+     *     "MODERATE: [threshold2, threshold3)"
+     *     "GOOD:  [threshold3, threshold4)"
+     *     "EXCELLENT:  [threshold4, 3]"
+     *
+     * This key is considered invalid if the format is violated. If the key is invalid or
+     * not configured, a default value set will apply.
+     */
+    public static final String KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY =
+            "lte_rsrq_thresholds_int_array";
+
+    /**
+     * A list of 4 customized LTE Reference Signal Signal to Noise Ratio (RSSNR) thresholds.
+     *
+     * 4 threshold integers must be within the boundaries [-200, 300], and the levels are:
+     *     "NONE: [-200, threshold1)"
+     *     "POOR: [threshold1, threshold2)"
+     *     "MODERATE: [threshold2, threshold3)"
+     *     "GOOD:  [threshold3, threshold4)"
+     *     "EXCELLENT:  [threshold4, 300]"
+     * Note: the unit of the values is 10*db; it is derived by multiplying 10 on the original dB
+     * value reported by modem.
+     *
+     * This key is considered invalid if the format is violated. If the key is invalid or
+     * not configured, a default value set will apply.
+     */
+    public static final String KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY =
+            "lte_rssnr_thresholds_int_array";
+
+    /**
      * Decides when clients try to bind to iwlan network service, which package name will
      * the binding intent go to.
      * @hide
@@ -3749,6 +3813,20 @@
                         -108, /* SIGNAL_STRENGTH_GOOD */
                         -98,  /* SIGNAL_STRENGTH_GREAT */
                 });
+        sDefaults.putIntArray(KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY,
+                new int[] {
+                        -19, /* SIGNAL_STRENGTH_POOR */
+                        -17, /* SIGNAL_STRENGTH_MODERATE */
+                        -14, /* SIGNAL_STRENGTH_GOOD */
+                        -12  /* SIGNAL_STRENGTH_GREAT */
+                });
+        sDefaults.putIntArray(KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY,
+                new int[] {
+                        -30, /* SIGNAL_STRENGTH_POOR */
+                        10,  /* SIGNAL_STRENGTH_MODERATE */
+                        45,  /* SIGNAL_STRENGTH_GOOD */
+                        130  /* SIGNAL_STRENGTH_GREAT */
+                });
         sDefaults.putIntArray(KEY_WCDMA_RSCP_THRESHOLDS_INT_ARRAY,
                 new int[] {
                         -115,  /* SIGNAL_STRENGTH_POOR */
@@ -3852,6 +3930,34 @@
                 new int[] {4 /* BUSY */});
         sDefaults.putBoolean(KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL, false);
         sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG, 2000);
+        sDefaults.putInt(KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT,
+                CellSignalStrengthLte.USE_RSRP | CellSignalStrengthLte.USE_RSSNR);
+        // Default wifi configurations.
+        sDefaults.putAll(Wifi.getDefaults());
+    }
+
+    /**
+     * Wi-Fi configs used in WiFi Module.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class Wifi {
+        /** Prefix of all Wifi.KEY_* constants. */
+        public static final String KEY_PREFIX = "wifi.";
+        /**
+        * It contains the maximum client count definition that the carrier owns.
+        */
+        public static final String KEY_HOTSPOT_MAX_CLIENT_COUNT =
+                KEY_PREFIX + "hotspot_maximum_client_count";
+
+        private static PersistableBundle getDefaults() {
+            PersistableBundle defaults = new PersistableBundle();
+            defaults.putInt(KEY_HOTSPOT_MAX_CLIENT_COUNT, 0);
+            return defaults;
+        }
+
+        private Wifi() {}
     }
 
     /**
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index 8336d1b..8df9d23 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -55,6 +55,25 @@
     private static final int MAX_LTE_RSRP = -44;
     private static final int MIN_LTE_RSRP = -140;
 
+    /**
+     * Indicates RSRP is considered for {@link #getLevel()} and reported from modem.
+     *
+     * @hide
+     */
+    public static final int USE_RSRP = 1 << 0;
+    /**
+     * Indicates RSRQ is considered for {@link #getLevel()} and reported from modem.
+     *
+     * @hide
+     */
+    public static final int USE_RSRQ = 1 << 1;
+    /**
+     * Indicates RSSNR is considered for {@link #getLevel()} and reported from modem.
+     *
+     * @hide
+     */
+    public static final int USE_RSSNR = 1 << 2;
+
     @UnsupportedAppUsage(maxTargetSdk = android.os.Build.VERSION_CODES.P)
     private int mSignalStrength; // To be removed
     private int mRssi;
@@ -70,6 +89,21 @@
     private int mTimingAdvance;
     private int mLevel;
 
+    /**
+     * Bit-field integer to determine whether to use Reference Signal Received Power (RSRP),
+     * Reference Signal Received Quality (RSRQ), and/or Reference Signal Signal to Noise Ratio
+     * (RSSNR) for the number of LTE signal bars. If multiple measures are set, the parameter
+     * whose signal level value is smallest is used to indicate the signal level.
+     *
+     *  RSRP = 1 << 0,
+     *  RSRQ = 1 << 1,
+     *  RSSNR = 1 << 2,
+     *
+     * For example, if both RSRP and RSRQ are used, the value of key is 3 (1 << 0 | 1 << 1).
+     * If the key is invalid or not configured, a default value (RSRP = 1 << 0) will apply.
+     */
+    private int mParametersUseForLevel;
+
     /** @hide */
     @UnsupportedAppUsage
     public CellSignalStrengthLte() {
@@ -81,7 +115,7 @@
      *
      * @param rssi in dBm [-113,-51], UNKNOWN
      * @param rsrp in dBm [-140,-43], UNKNOWN
-     * @param rsrq in dB [-20,-3], UNKNOWN
+     * @param rsrq in dB [-34, 3], UNKNOWN
      * @param rssnr in 10*dB [-200, +300], UNKNOWN
      * @param cqi [0, 15], UNKNOWN
      * @param timingAdvance [0, 1282], UNKNOWN
@@ -94,7 +128,7 @@
         mRssi = inRangeOrUnavailable(rssi, -113, -51);
         mSignalStrength = mRssi;
         mRsrp = inRangeOrUnavailable(rsrp, -140, -43);
-        mRsrq = inRangeOrUnavailable(rsrq, -20, -3);
+        mRsrq = inRangeOrUnavailable(rsrq, -34, 3);
         mRssnr = inRangeOrUnavailable(rssnr, -200, 300);
         mCqi = inRangeOrUnavailable(cqi, 0, 15);
         mTimingAdvance = inRangeOrUnavailable(timingAdvance, 0, 1282);
@@ -125,6 +159,7 @@
         mCqi = s.mCqi;
         mTimingAdvance = s.mTimingAdvance;
         mLevel = s.mLevel;
+        mParametersUseForLevel = s.mParametersUseForLevel;
     }
 
     /** @hide */
@@ -144,6 +179,7 @@
         mCqi = CellInfo.UNAVAILABLE;
         mTimingAdvance = CellInfo.UNAVAILABLE;
         mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+        mParametersUseForLevel = USE_RSRP | USE_RSSNR;
     }
 
     /** {@inheritDoc} */
@@ -154,102 +190,153 @@
     }
 
     // Lifted from Default carrier configs and max range of RSRP
-    private static final int[] sThresholds = new int[]{-115, -105, -95, -85};
+    private static final int[] sRsrpThresholds = new int[] {
+            -115, /* SIGNAL_STRENGTH_POOR */
+            -105, /* SIGNAL_STRENGTH_MODERATE */
+            -95,  /* SIGNAL_STRENGTH_GOOD */
+            -85   /* SIGNAL_STRENGTH_GREAT */
+    };
+
+    // Lifted from Default carrier configs and max range of RSRQ
+    private static final int[] sRsrqThresholds = new int[] {
+            -19, /* SIGNAL_STRENGTH_POOR */
+            -17, /* SIGNAL_STRENGTH_MODERATE */
+            -14, /* SIGNAL_STRENGTH_GOOD */
+            -12  /* SIGNAL_STRENGTH_GREAT */
+    };
+    // Lifted from Default carrier configs and max range of RSSNR
+    private static final int[] sRssnrThresholds = new int[] {
+            -30, /* SIGNAL_STRENGTH_POOR */
+            10,  /* SIGNAL_STRENGTH_MODERATE */
+            45,  /* SIGNAL_STRENGTH_GOOD */
+            130  /* SIGNAL_STRENGTH_GREAT */
+    };
     private static final int sRsrpBoost = 0;
 
+    /**
+     * Checks if the given parameter type is considered to use for {@link #getLevel()}.
+     *
+     * Note: if multiple parameter types are considered, the smaller level for one of the
+     * parameters would be returned by {@link #getLevel()}
+     *
+     * @param parameterType bitwise OR of {@link #USE_RSRP}, {@link #USE_RSRQ},
+     *         {@link #USE_RSSNR}
+     * @return {@code true} if the level is calculated based on the given parameter type;
+     *      {@code false} otherwise.
+     */
+    private boolean isLevelForParameter(int parameterType) {
+        return (parameterType & mParametersUseForLevel) == parameterType;
+    }
+
     /** @hide */
     @Override
     public void updateLevel(PersistableBundle cc, ServiceState ss) {
-        int[] thresholds;
+        int[] rsrpThresholds, rsrqThresholds, rssnrThresholds;
         boolean rsrpOnly;
         if (cc == null) {
-            thresholds = sThresholds;
+            mParametersUseForLevel = USE_RSRP | USE_RSSNR;
+            rsrpThresholds = sRsrpThresholds;
+            rsrqThresholds = sRsrqThresholds;
+            rssnrThresholds = sRssnrThresholds;
             rsrpOnly = false;
         } else {
+            mParametersUseForLevel = cc.getInt(
+                    CarrierConfigManager.KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT);
+            Rlog.i(LOG_TAG, "Using signal strength level: " + mParametersUseForLevel);
+            rsrpThresholds = cc.getIntArray(
+                    CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY);
+            if (rsrpThresholds == null) rsrpThresholds = sRsrpThresholds;
+            Rlog.i(LOG_TAG, "Applying LTE RSRP Thresholds: " + Arrays.toString(rsrpThresholds));
+            rsrqThresholds = cc.getIntArray(
+                    CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY);
+            if (rsrqThresholds == null) rsrqThresholds = sRsrqThresholds;
+            Rlog.i(LOG_TAG, "Applying LTE RSRQ Thresholds: " + Arrays.toString(rsrqThresholds));
+            rssnrThresholds = cc.getIntArray(
+                    CarrierConfigManager.KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY);
+            if (rssnrThresholds == null) rssnrThresholds = sRssnrThresholds;
+            Rlog.i(LOG_TAG, "Applying LTE RSSNR Thresholds: " + Arrays.toString(rssnrThresholds));
             rsrpOnly = cc.getBoolean(
                     CarrierConfigManager.KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL, false);
-            thresholds = cc.getIntArray(
-                    CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY);
-            if (thresholds == null) thresholds = sThresholds;
-            if (DBG) log("updateLevel() carrierconfig - rsrpOnly="
-                    + rsrpOnly + ", thresholds=" + Arrays.toString(thresholds));
         }
 
-
         int rsrpBoost = 0;
         if (ss != null) {
             rsrpBoost = ss.getLteEarfcnRsrpBoost();
         }
 
-        int rssiIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
-        int rsrpIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
-        int snrIconLevel = -1;
-
-        int rsrp = mRsrp + rsrpBoost;
-
-        if (rsrp < MIN_LTE_RSRP || rsrp > MAX_LTE_RSRP) {
-            rsrpIconLevel = -1;
-        } else {
-            rsrpIconLevel = thresholds.length;
-            while (rsrpIconLevel > 0 && rsrp < thresholds[rsrpIconLevel - 1]) rsrpIconLevel--;
-        }
+        int rsrp = inRangeOrUnavailable(mRsrp + rsrpBoost, MIN_LTE_RSRP, MAX_LTE_RSRP);
 
         if (rsrpOnly) {
-            if (DBG) log("updateLevel() - rsrp = " + rsrpIconLevel);
-            if (rsrpIconLevel != -1) {
-                mLevel = rsrpIconLevel;
+            int level = updateLevelWithMeasure(rsrp, rsrpThresholds);
+            if (DBG) log("updateLevel() - rsrp = " + level);
+            if (level != SignalStrength.INVALID) {
+                mLevel = level;
                 return;
             }
         }
 
-        /*
-         * Values are -200 dB to +300 (SNR*10dB) RS_SNR >= 13.0 dB =>4 bars 4.5
-         * dB <= RS_SNR < 13.0 dB => 3 bars 1.0 dB <= RS_SNR < 4.5 dB => 2 bars
-         * -3.0 dB <= RS_SNR < 1.0 dB 1 bar RS_SNR < -3.0 dB/No Service Antenna
-         * Icon Only
-         */
-        if (mRssnr > 300) snrIconLevel = -1;
-        else if (mRssnr >= 130) snrIconLevel = SIGNAL_STRENGTH_GREAT;
-        else if (mRssnr >= 45) snrIconLevel = SIGNAL_STRENGTH_GOOD;
-        else if (mRssnr >= 10) snrIconLevel = SIGNAL_STRENGTH_MODERATE;
-        else if (mRssnr >= -30) snrIconLevel = SIGNAL_STRENGTH_POOR;
-        else if (mRssnr >= -200)
-            snrIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+        int rsrpLevel = SignalStrength.INVALID;
+        int rsrqLevel = SignalStrength.INVALID;
+        int rssnrLevel = SignalStrength.INVALID;
 
-        if (DBG) log("updateLevel() - rsrp:" + mRsrp + " snr:" + mRssnr + " rsrpIconLevel:"
-                + rsrpIconLevel + " snrIconLevel:" + snrIconLevel
-                + " lteRsrpBoost:" + sRsrpBoost);
-
-        /* Choose a measurement type to use for notification */
-        if (snrIconLevel != -1 && rsrpIconLevel != -1) {
-            /*
-             * The number of bars displayed shall be the smaller of the bars
-             * associated with LTE RSRP and the bars associated with the LTE
-             * RS_SNR
-             */
-            mLevel = (rsrpIconLevel < snrIconLevel ? rsrpIconLevel : snrIconLevel);
-            return;
+        if (isLevelForParameter(USE_RSRP)) {
+            rsrpLevel = updateLevelWithMeasure(rsrp, rsrpThresholds);
+            Rlog.i(LOG_TAG, "Updated 4G LTE RSRP Level: " + rsrpLevel);
         }
-
-        if (snrIconLevel != -1) {
-            mLevel = snrIconLevel;
-            return;
+        if (isLevelForParameter(USE_RSRQ)) {
+            rsrqLevel = updateLevelWithMeasure(mRsrq, rsrqThresholds);
+            Rlog.i(LOG_TAG, "Updated 4G LTE RSRQ Level: " + rsrqLevel);
         }
-
-        if (rsrpIconLevel != -1) {
-            mLevel = rsrpIconLevel;
-            return;
+        if (isLevelForParameter(USE_RSSNR)) {
+            rssnrLevel = updateLevelWithMeasure(mRssnr, rssnrThresholds);
+            Rlog.i(LOG_TAG, "Updated 4G LTE RSSNR Level: " + rssnrLevel);
         }
+        // Apply the smaller value among three levels of three measures.
+        mLevel = Math.min(Math.min(rsrpLevel, rsrqLevel), rssnrLevel);
 
-        if (mRssi > -51) rssiIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
-        else if (mRssi >= -89) rssiIconLevel = SIGNAL_STRENGTH_GREAT;
-        else if (mRssi >= -97) rssiIconLevel = SIGNAL_STRENGTH_GOOD;
-        else if (mRssi >= -103) rssiIconLevel = SIGNAL_STRENGTH_MODERATE;
-        else if (mRssi >= -113) rssiIconLevel = SIGNAL_STRENGTH_POOR;
-        else rssiIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
-        if (DBG) log("getLteLevel - rssi:" + mRssi + " rssiIconLevel:"
-                + rssiIconLevel);
-        mLevel = rssiIconLevel;
+        if (mLevel == SignalStrength.INVALID) {
+            int rssiLevel;
+            if (mRssi > -51) {
+                rssiLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+            } else if (mRssi >= -89) {
+                rssiLevel = SIGNAL_STRENGTH_GREAT;
+            } else if (mRssi >= -97) {
+                rssiLevel = SIGNAL_STRENGTH_GOOD;
+            } else if (mRssi >= -103) {
+                rssiLevel = SIGNAL_STRENGTH_MODERATE;
+            } else if (mRssi >= -113) {
+                rssiLevel = SIGNAL_STRENGTH_POOR;
+            } else {
+                rssiLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+            }
+            if (DBG) log("getLteLevel - rssi:" + mRssi + " rssiIconLevel:" + rssiLevel);
+            mLevel = rssiLevel;
+        }
+    }
+
+    /**
+     * Update level with corresponding measure and thresholds.
+     *
+     * @param measure corresponding signal measure
+     * @param thresholds corresponding signal thresholds
+     * @return level of the signal strength
+     */
+    private int updateLevelWithMeasure(int measure, int[] thresholds) {
+        int level;
+        if (measure == CellInfo.UNAVAILABLE) {
+            level = SignalStrength.INVALID;
+        } else if (measure >= thresholds[3]) {
+            level = SIGNAL_STRENGTH_GREAT;
+        } else if (measure >= thresholds[2]) {
+            level = SIGNAL_STRENGTH_GOOD;
+        } else if (measure >= thresholds[1]) {
+            level = SIGNAL_STRENGTH_MODERATE;
+        } else if (measure >= thresholds[0]) {
+            level = SIGNAL_STRENGTH_POOR;
+        } else {
+            level = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+        }
+        return level;
     }
 
     /**
@@ -386,7 +473,8 @@
                 + " rssnr=" + mRssnr
                 + " cqi=" + mCqi
                 + " ta=" + mTimingAdvance
-                + " level=" + mLevel;
+                + " level=" + mLevel
+                + " parametersUseForLevel=" + mParametersUseForLevel;
     }
 
     /** Implement the Parcelable interface */
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index 78ad5c5..0610796 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -81,18 +81,20 @@
 
 
     /**
-     * Constructor
+     * Constructor of PreciseDataConnectionState
      *
      * @param state the state of the data connection
      * @param networkType the access network that is/would carry this data connection
      * @param apnTypes the APN types that this data connection carries
-     * @param apnSetting if there is a valid APN for this Data Connection, then the APN Settings;
-     *        if there is no valid APN setting for the specific type, then this will be null
+     * @param apn the APN of this data connection
      * @param linkProperties if the data connection is connected, the properties of the connection
      * @param failCause in case a procedure related to this data connection fails, a non-zero error
      *        code indicating the cause of the failure.
+     * @param apnSetting if there is a valid APN for this Data Connection, then the APN Settings;
+     *        if there is no valid APN setting for the specific type, then this will be null
      * @hide
      */
+    @SystemApi
     public PreciseDataConnectionState(@DataState int state,
                                       @NetworkType int networkType,
                                       @ApnType int apnTypes, @NonNull String apn,
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index cab5286..7215ef8 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -16,6 +16,7 @@
 
 package android.telephony;
 
+import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -1635,14 +1636,16 @@
      * operation is performed on the correct subscription.
      * </p>
      *
-     * @param messageIndex is the record index of the message on ICC
-     * @return true for success
+     * @param messageIndex This is the same index used to access a message
+     * from {@link #getMessagesFromIcc()}.
+     * @return true for success, false if the operation fails. Failure can be due to IPC failure,
+     * RIL/modem error which results in SMS failed to be deleted on SIM
      *
      * {@hide}
      */
-    @UnsupportedAppUsage
-    public boolean
-    deleteMessageFromIcc(int messageIndex) {
+    @SystemApi
+    @RequiresPermission(Manifest.permission.ACCESS_MESSAGES_ON_ICC)
+    public boolean deleteMessageFromIcc(int messageIndex) {
         boolean success = false;
 
         try {
@@ -1684,6 +1687,7 @@
      * {@hide}
      */
     @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.ACCESS_MESSAGES_ON_ICC)
     public boolean updateMessageOnIcc(int messageIndex, int newStatus, byte[] pdu) {
         boolean success = false;
 
@@ -1716,8 +1720,22 @@
      * operation is performed on the correct subscription.
      * </p>
      *
+     * @return <code>List</code> of <code>SmsMessage</code> objects
+     *
+     * {@hide}
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.ACCESS_MESSAGES_ON_ICC)
+    public @NonNull List<SmsMessage> getMessagesFromIcc() {
+        return getAllMessagesFromIcc();
+    }
+
+    /**
      * @return <code>ArrayList</code> of <code>SmsMessage</code> objects
      *
+     * This is similar to {@link #getMessagesFromIcc} except that it will return ArrayList.
+     * Suggested to use {@link #getMessagesFromIcc} instead.
+     *
      * {@hide}
      */
     @UnsupportedAppUsage
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index 392d3eb..2897358 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -1038,10 +1038,10 @@
     }
 
     /**
-     * {@hide}
      * Returns the recipient address(receiver) of this SMS message in String form or null if
      * unavailable.
      */
+    @Nullable
     public String getRecipientAddress() {
         return mWrappedSmsMessage.getRecipientAddress();
     }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index f08e1ec..15398ca 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3175,6 +3175,34 @@
     }
 
     /**
+     * Set uicc applications being enabled or disabled.
+     * The value will be remembered on the subscription and will be applied whenever it's present.
+     * If the subscription in currently present, it will also apply the setting to modem
+     * immediately.
+     *
+     * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
+     *
+     * @param enabled whether uicc applications are enabled or disabled.
+     * @param subscriptionId which subscription to operate on.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    public void setUiccApplicationsEnabled(boolean enabled, int subscriptionId) {
+        if (VDBG) {
+            logd("setUiccApplicationsEnabled subId= " + subscriptionId + " enable " + enabled);
+        }
+        try {
+            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            if (iSub != null) {
+                iSub.setUiccApplicationsEnabled(enabled, subscriptionId);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+    }
+
+    /**
      * Whether it's supported to disable / re-enable a subscription on a physical (non-euicc) SIM.
      *
      * Physical SIM refers non-euicc, or aka non-programmable SIM.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a96325e..39e57b7 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4243,7 +4243,7 @@
      * The returned set of subscriber IDs will include the subscriber ID corresponding to this
      * TelephonyManager's subId.
      *
-     * This is deprecated and {@link #getMergedSubscriberIdsFromGroup()} should be used for data
+     * This is deprecated and {@link #getMergedImsisFromGroup()} should be used for data
      * usage merging purpose.
      * TODO: remove this API.
      *
@@ -4264,25 +4264,27 @@
     }
 
     /**
-     * Return the set of subscriber IDs that should be considered "merged together" for data usage
-     * purposes. Unlike {@link #getMergedSubscriberIds()} this API merge subscriberIds based on
-     * subscription grouping: subscriberId of those in the same group will all be returned.
+     * Return the set of IMSIs that should be considered "merged together" for data usage
+     * purposes. Unlike {@link #getMergedSubscriberIds()} this API merge IMSIs based on
+     * subscription grouping: IMSI of those in the same group will all be returned.
+     * Return the current IMSI if there is no subscription group.
      *
      * <p>Requires the calling app to have READ_PRIVILEGED_PHONE_STATE permission.
      *
      * @hide
      */
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-    public @Nullable String[] getMergedSubscriberIdsFromGroup() {
+    public @NonNull String[] getMergedImsisFromGroup() {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.getMergedSubscriberIdsFromGroup(getSubId(), getOpPackageName());
+                return telephony.getMergedImsisFromGroup(getSubId(), getOpPackageName());
             }
         } catch (RemoteException ex) {
         } catch (NullPointerException ex) {
         }
-        return null;
+        return new String[0];
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index c5d58ac..cc02a40 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -300,4 +300,6 @@
     int getActiveDataSubscriptionId();
 
     boolean canDisablePhysicalSubscription();
+
+    int setUiccApplicationsEnabled(boolean enabled, int subscriptionId);
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0baac71..b99fe904 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1136,7 +1136,7 @@
     /**
      * @hide
      */
-    String[] getMergedSubscriberIdsFromGroup(int subId, String callingPackage);
+    String[] getMergedImsisFromGroup(int subId, String callingPackage);
 
     /**
      * Override the operator branding for the current ICCID.
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index 024b640..08c536b 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -173,24 +173,6 @@
             = "android.intent.action.ANY_DATA_STATE";
 
     /**
-     * Broadcast Action: An attempt to establish a data connection has failed.
-     * The intent will have the following extra values:</p>
-     * <dl>
-     *   <dt>phoneName</dt><dd>A string version of the phone name.</dd>
-     *   <dt>state</dt><dd>One of {@code CONNECTED}, {@code CONNECTING}, or {code DISCONNECTED}.</dd>
-     *   <dt>reason</dt><dd>A string indicating the reason for the failure, if available.</dd>
-     * </dl>
-     *
-     * <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_DATA_CONNECTION_FAILED
-            = "android.intent.action.DATA_CONNECTION_FAILED";
-
-    /**
      * Broadcast Action: The sim card state has changed.
      * The intent will have the following extra values:</p>
      * <dl>
diff --git a/tests/UsbManagerTests/Android.bp b/tests/UsbManagerTests/Android.bp
new file mode 100644
index 0000000..a03c6e2
--- /dev/null
+++ b/tests/UsbManagerTests/Android.bp
@@ -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.
+//
+
+android_test {
+    name: "UsbManagerTests",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "frameworks-base-testutils",
+        "androidx.test.rules",
+        "mockito-target-inline-minus-junit4",
+        "platform-test-annotations",
+        "truth-prebuilt",
+        "UsbManagerTestLib",
+    ],
+    jni_libs: ["libdexmakerjvmtiagent"],
+    certificate: "platform",
+    platform_apis: true,
+    test_suites: ["device-tests"],
+}
diff --git a/tests/UsbManagerTests/AndroidManifest.xml b/tests/UsbManagerTests/AndroidManifest.xml
new file mode 100644
index 0000000..4e0b790
--- /dev/null
+++ b/tests/UsbManagerTests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.server.usbtest" >
+
+    <uses-permission android:name="android.permission.MANAGE_USB" />
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.usbtest"
+                     android:label="UsbManagerTests"/>
+</manifest>
diff --git a/tests/UsbManagerTests/AndroidTest.xml b/tests/UsbManagerTests/AndroidTest.xml
new file mode 100644
index 0000000..c6e22cd
--- /dev/null
+++ b/tests/UsbManagerTests/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?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="Runs Frameworks USB API instrumentation Tests.">
+    <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/>
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="UsbManagerTests.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/>
+    <option name="test-suite-tag" value="apct"/>
+    <option name="test-tag" value="UsbManagerTests" />
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.server.usbtest"/>
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/tests/UsbManagerTests/lib/Android.bp b/tests/UsbManagerTests/lib/Android.bp
new file mode 100644
index 0000000..3c5d91b
--- /dev/null
+++ b/tests/UsbManagerTests/lib/Android.bp
@@ -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.
+//
+
+android_library {
+    name: "UsbManagerTestLib",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "frameworks-base-testutils",
+        "androidx.test.rules",
+        "mockito-target-inline-minus-junit4",
+        "platform-test-annotations",
+        "services.core",
+        "services.net",
+        "services.usb",
+        "truth-prebuilt",
+        "androidx.core_core",
+    ],
+    libs: [
+        "android.test.mock",
+    ],
+}
diff --git a/tests/UsbManagerTests/lib/AndroidManifest.xml b/tests/UsbManagerTests/lib/AndroidManifest.xml
new file mode 100644
index 0000000..c8b301c
--- /dev/null
+++ b/tests/UsbManagerTests/lib/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?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.server.usblib">
+
+    <application/>
+
+</manifest>
diff --git a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
new file mode 100644
index 0000000..782439f
--- /dev/null
+++ b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
@@ -0,0 +1,129 @@
+/*
+ * 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.usblib;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.usb.UsbManager;
+import android.os.RemoteException;
+import android.util.Log;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests lib for {@link android.hardware.usb.UsbManager}.
+ */
+public class UsbManagerTestLib {
+    private static final String TAG = UsbManagerTestLib.class.getSimpleName();
+
+    private Context mContext;
+
+    private UsbManager mUsbManagerSys;
+    private UsbManager mUsbManagerMock;
+    @Mock private android.hardware.usb.IUsbManager mMockUsbService;
+
+    public UsbManagerTestLib(Context context) {
+        MockitoAnnotations.initMocks(this);
+        mContext = context;
+
+        assertNotNull(mUsbManagerSys = mContext.getSystemService(UsbManager.class));
+        assertNotNull(mUsbManagerMock = new UsbManager(mContext, mMockUsbService));
+    }
+
+    private long getCurrentFunctions() {
+        return mUsbManagerMock.getCurrentFunctions();
+    }
+
+    private void setCurrentFunctions(long functions) {
+        mUsbManagerMock.setCurrentFunctions(functions);
+    }
+
+    private long getCurrentFunctionsSys() {
+        return mUsbManagerSys.getCurrentFunctions();
+    }
+
+    private void setCurrentFunctionsSys(long functions) {
+        mUsbManagerSys.setCurrentFunctions(functions);
+    }
+
+    private void testSetGetCurrentFunctions_Matched(long functions) {
+        setCurrentFunctions(functions);
+        assertEquals("CurrentFunctions mismatched: ", functions, getCurrentFunctions());
+    }
+
+    private void testGetCurrentFunctionsMock_Matched(long functions) {
+        try {
+            when(mMockUsbService.getCurrentFunctions()).thenReturn(functions);
+
+            assertEquals("CurrentFunctions mismatched: ", functions, getCurrentFunctions());
+        } catch (RemoteException remEx) {
+            Log.w(TAG, "RemoteException");
+        }
+    }
+
+    private void testSetCurrentFunctionsMock_Matched(long functions) {
+        try {
+            setCurrentFunctions(functions);
+
+            verify(mMockUsbService).setCurrentFunctions(eq(functions));
+        } catch (RemoteException remEx) {
+            Log.w(TAG, "RemoteException");
+        }
+    }
+
+    public void testGetCurrentFunctionsSysEx() throws Exception {
+        getCurrentFunctionsSys();
+    }
+
+    public void testSetCurrentFunctionsSysEx(long functions) throws Exception {
+        setCurrentFunctionsSys(functions);
+    }
+
+    public void testGetCurrentFunctionsEx() throws Exception {
+        getCurrentFunctions();
+
+        verify(mMockUsbService).getCurrentFunctions();
+    }
+
+    public void testSetCurrentFunctionsEx(long functions) throws Exception {
+        setCurrentFunctions(functions);
+
+        verify(mMockUsbService).setCurrentFunctions(eq(functions));
+    }
+
+    public void testGetCurrentFunctions_shouldMatched() {
+        testGetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_NONE);
+        testGetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_MTP);
+        testGetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_PTP);
+        testGetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_MIDI);
+        testGetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_RNDIS);
+    }
+
+    public void testSetCurrentFunctions_shouldMatched() {
+        testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_NONE);
+        testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_MTP);
+        testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_PTP);
+        testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_MIDI);
+        testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_RNDIS);
+    }
+}
diff --git a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java
new file mode 100644
index 0000000..8b21763
--- /dev/null
+++ b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java
@@ -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.server.usbtest;
+
+import android.content.Context;
+import android.hardware.usb.UsbManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.android.server.usblib.UsbManagerTestLib;
+
+/**
+ * Unit tests for {@link android.hardware.usb.UsbManager}.
+ * Note: MUST claimed MANAGE_USB permission in Manifest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UsbManagerApiTest {
+    private Context mContext;
+
+    private final UsbManagerTestLib mUsbManagerTestLib =
+            new UsbManagerTestLib(mContext = InstrumentationRegistry.getContext());
+
+    /**
+     * Verify NO SecurityException
+     * Go through System Server
+     */
+    @Test
+    public void testUsbApi_GetCurrentFunctionsSys_shouldNoSecurityException() throws Exception {
+        mUsbManagerTestLib.testGetCurrentFunctionsSysEx();
+    }
+
+    /**
+     * Verify NO SecurityException
+     * Go through System Server
+     */
+    @Test
+    public void testUsbApi_SetCurrentFunctionsSys_shouldNoSecurityException() throws Exception {
+        mUsbManagerTestLib.testSetCurrentFunctionsSysEx(UsbManager.FUNCTION_NONE);
+    }
+
+    /**
+     * Verify NO SecurityException
+     * Go through Direct API, will not be denied by @RequiresPermission annotation
+     */
+    @Test
+    public void testUsbApi_GetCurrentFunctions_shouldNoSecurityException() throws Exception {
+        mUsbManagerTestLib.testGetCurrentFunctionsEx();
+    }
+
+    /**
+     * Verify NO SecurityException
+     * Go through Direct API, will not be denied by @RequiresPermission annotation
+     */
+    @Test
+    public void testUsbApi_SetCurrentFunctions_shouldNoSecurityException() throws Exception {
+        mUsbManagerTestLib.testSetCurrentFunctionsEx(UsbManager.FUNCTION_NONE);
+    }
+
+    /**
+     * Verify API path from UsbManager to UsbService
+     */
+    @Test
+    public void testUsbApi_GetCurrentFunctions_shouldMatched() {
+        mUsbManagerTestLib.testGetCurrentFunctions_shouldMatched();
+    }
+
+    /**
+     * Verify API path from UsbManager to UsbService
+     */
+    @Test
+    public void testUsbApi_SetCurrentFunctions_shouldMatched() {
+        mUsbManagerTestLib.testSetCurrentFunctions_shouldMatched();
+    }
+}
diff --git a/tests/UsbTests/Android.bp b/tests/UsbTests/Android.bp
index 1b2cf63..7c2be9b 100644
--- a/tests/UsbTests/Android.bp
+++ b/tests/UsbTests/Android.bp
@@ -26,6 +26,7 @@
         "services.net",
         "services.usb",
         "truth-prebuilt",
+        "UsbManagerTestLib",
     ],
     jni_libs: ["libdexmakerjvmtiagent"],
     certificate: "platform",
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbManagerNoPermTest.java b/tests/UsbTests/src/com/android/server/usb/UsbManagerNoPermTest.java
new file mode 100644
index 0000000..a0fd9d4
--- /dev/null
+++ b/tests/UsbTests/src/com/android/server/usb/UsbManagerNoPermTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.usb;
+
+import android.content.Context;
+import android.hardware.usb.UsbManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.android.server.usblib.UsbManagerTestLib;
+
+/**
+ * Unit tests for {@link android.hardware.usb.UsbManager}.
+ * Note: NOT claimed MANAGE_USB permission in Manifest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UsbManagerNoPermTest {
+    private Context mContext;
+
+    private final UsbManagerTestLib mUsbManagerTestLib =
+            new UsbManagerTestLib(mContext = InstrumentationRegistry.getContext());
+
+    /**
+     * Verify SecurityException resulting from required permissions missing
+     * Go through System Server
+     */
+    @Test(expected = SecurityException.class)
+    public void testUsbApi_GetCurrentFunctionsSys_OnSecurityException() throws Exception {
+        mUsbManagerTestLib.testGetCurrentFunctionsSysEx();
+    }
+
+    /**
+     * Verify SecurityException resulting from required permissions missing
+     * Go through System Server
+     */
+    @Test(expected = SecurityException.class)
+    public void testUsbApi_SetCurrentFunctionsSys_OnSecurityException() throws Exception {
+        mUsbManagerTestLib.testSetCurrentFunctionsSysEx(UsbManager.FUNCTION_NONE);
+    }
+
+    /**
+     * Verify SecurityException resulting from required permissions missing
+     * Go through Direct API, will not be denied by @RequiresPermission annotation
+     */
+    @Test(expected = SecurityException.class)
+    @Ignore
+    public void testUsbApi_GetCurrentFunctions_OnSecurityException() throws Exception {
+        mUsbManagerTestLib.testGetCurrentFunctionsEx();
+    }
+
+    /**
+     * Verify SecurityException resulting from required permissions missing
+     * Go through Direct API, will not be denied by @RequiresPermission annotation
+     */
+    @Test(expected = SecurityException.class)
+    @Ignore
+    public void testUsbApi_SetCurrentFunctions_OnSecurityException() throws Exception {
+        mUsbManagerTestLib.testSetCurrentFunctionsEx(UsbManager.FUNCTION_NONE);
+    }
+}
diff --git a/tests/libs-permissions/Android.bp b/tests/libs-permissions/Android.bp
index 330bfc9..66a1f83 100644
--- a/tests/libs-permissions/Android.bp
+++ b/tests/libs-permissions/Android.bp
@@ -2,6 +2,7 @@
     name: "com.android.test.libs.product",
     installable: true,
     product_specific: true,
+    sdk_version: "current",
     srcs: ["product/java/**/*.java"],
     required: ["com.android.test.libs.product.xml"],
 }
diff --git a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
index 4fa93ee..8ccf007 100644
--- a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
+++ b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
@@ -28,29 +28,27 @@
 
 /**
  * Easy Connect (DPP) Status Callback. Use this callback to get status updates (success, failure,
- * progress) from the Easy Connect operation started with
- * {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String, int, int, Executor,
- * EasyConnectStatusCallback)} or {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String,
- * Executor, EasyConnectStatusCallback)}
- *
- * @hide
+ * progress) from the Easy Connect operations.
  */
-@SystemApi
 public abstract class EasyConnectStatusCallback {
     /**
      * Easy Connect R1 Success event: Configuration sent (Configurator mode). This is the last
      * and final Easy Connect event when either the local device or remote device implement R1.
      * If both devices implement R2, this event will never be received, and the
-     * {@link EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED} will be received.
+     * {@link #EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED} will be received.
+     * @hide
      */
+    @SystemApi
     public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT = 0;
 
     /**
      * East Connect R2 Success event: Configuration applied by Enrollee (Configurator mode).
      * This is the last and final Easy Connect event when both the local device and remote device
      * implement R2. If either the local device or remote device implement R1, this event will never
-     * be received, and the {@link EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT} will be received.
+     * be received, and the {@link #EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT} will be received.
+     * @hide
      */
+    @SystemApi
     public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED = 1;
 
     /** @hide */
@@ -64,22 +62,30 @@
 
     /**
      * Easy Connect Progress event: Initial authentication with peer succeeded.
+     * @hide
      */
+    @SystemApi
     public static final int EASY_CONNECT_EVENT_PROGRESS_AUTHENTICATION_SUCCESS = 0;
 
     /**
      * Easy Connect Progress event: Peer requires more time to process bootstrapping.
+     * @hide
      */
+    @SystemApi
     public static final int EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING = 1;
 
     /**
      * Easy Connect R2 Progress event: Configuration sent to Enrollee, waiting for response
+     * @hide
      */
+    @SystemApi
     public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE = 2;
 
     /**
      * Easy Connect R2 Progress event: Configuration accepted by Enrollee, waiting for response
+     * @hide
      */
+    @SystemApi
     public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_ACCEPTED = 3;
 
     /** @hide */
@@ -174,6 +180,12 @@
     public @interface EasyConnectFailureStatusCode {
     }
 
+    /** @hide */
+    @SystemApi
+    public EasyConnectStatusCallback() {
+        // Fully-static utility classes must not have constructor
+    }
+
     /**
      * Called when local Easy Connect Enrollee successfully receives a new Wi-Fi configuration from
      * the
@@ -185,7 +197,9 @@
      * EasyConnectStatusCallback)} .
      *
      * @param newNetworkId New Wi-Fi configuration with a network ID received from the configurator
+     * @hide
      */
+    @SystemApi
     public abstract void onEnrolleeSuccess(int newNetworkId);
 
     /**
@@ -197,7 +211,9 @@
      * int, Executor,EasyConnectStatusCallback)}.
      *
      * @param code Easy Connect success status code.
+     * @hide
      */
+    @SystemApi
     public abstract void onConfiguratorSuccess(@EasyConnectSuccessStatusCode int code);
 
     /**
@@ -205,7 +221,9 @@
      * end of the current Easy Connect session, and no further callbacks will be called.
      *
      * @param code Easy Connect failure status code.
+     * @hide
      */
+    @SystemApi
     public void onFailure(@EasyConnectFailureStatusCode int code) {}
 
     /**
@@ -227,7 +245,9 @@
      * @param operatingClassArray Array of bands the Enrollee supports as expressed as the Global
      *                            Operating Class, see Table E-4 in IEEE Std 802.11-2016 - Global
      *                            operating classes.
+     * @hide
      */
+    @SystemApi
     public void onFailure(@EasyConnectFailureStatusCode int code, @Nullable String ssid,
             @NonNull SparseArray<int[]> channelListArray, @NonNull int[] operatingClassArray) {
         onFailure(code);
@@ -238,6 +258,8 @@
      * to show progress.
      *
      * @param code Easy Connect progress status code.
+     * @hide
      */
+    @SystemApi
     public abstract void onProgress(@EasyConnectProgressStatusCode int code);
 }
diff --git a/wifi/java/android/net/wifi/ISoftApCallback.aidl b/wifi/java/android/net/wifi/ISoftApCallback.aidl
index 452a655..482b491 100644
--- a/wifi/java/android/net/wifi/ISoftApCallback.aidl
+++ b/wifi/java/android/net/wifi/ISoftApCallback.aidl
@@ -15,6 +15,7 @@
  */
 
 package android.net.wifi;
+import android.net.wifi.SoftApCapability;
 import android.net.wifi.SoftApInfo;
 
 import android.net.wifi.WifiClient;
@@ -51,4 +52,12 @@
      * @param softApInfo is the softap information. {@link SoftApInfo}
      */
     void onInfoChanged(in SoftApInfo softApInfo);
+
+
+    /**
+     * Service to manager callback providing information of softap.
+     *
+     * @param capability is the softap capability. {@link SoftApCapability}
+     */
+    void onCapabilityChanged(in SoftApCapability capability);
 }
diff --git a/wifi/java/android/net/wifi/SoftApCapability.aidl b/wifi/java/android/net/wifi/SoftApCapability.aidl
new file mode 100644
index 0000000..bf30709
--- /dev/null
+++ b/wifi/java/android/net/wifi/SoftApCapability.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.wifi;
+
+parcelable SoftApCapability;
diff --git a/wifi/java/android/net/wifi/SoftApCapability.java b/wifi/java/android/net/wifi/SoftApCapability.java
new file mode 100644
index 0000000..506f493
--- /dev/null
+++ b/wifi/java/android/net/wifi/SoftApCapability.java
@@ -0,0 +1,114 @@
+/*
+ * 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.wifi;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A class representing capability of the SoftAp.
+ * {@see WifiManager}
+ *
+ * @hide
+ */
+@SystemApi
+public final class SoftApCapability implements Parcelable {
+
+    private int mMaximumSupportedClientNumber;
+
+    /**
+     * Get the maximum supported client numbers which AP resides on.
+     */
+    public int getMaxSupportedClients() {
+        return mMaximumSupportedClientNumber;
+    }
+
+    /**
+     * Set the maximum supported client numbers which AP resides on.
+     * @hide
+     */
+    public void setMaxSupportedClients(int maxClient) {
+        mMaximumSupportedClientNumber = maxClient;
+    }
+
+    /**
+     * @hide
+     */
+    public SoftApCapability(@Nullable SoftApCapability source) {
+        if (source != null) {
+            mMaximumSupportedClientNumber = source.mMaximumSupportedClientNumber;
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public SoftApCapability() {
+    }
+
+    @Override
+    /** Implement the Parcelable interface. */
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    /** Implement the Parcelable interface */
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mMaximumSupportedClientNumber);
+    }
+
+    @NonNull
+    /** Implement the Parcelable interface */
+    public static final Creator<SoftApCapability> CREATOR = new Creator<SoftApCapability>() {
+        public SoftApCapability createFromParcel(Parcel in) {
+            SoftApCapability capability = new SoftApCapability();
+            capability.mMaximumSupportedClientNumber = in.readInt();
+            return capability;
+        }
+
+        public SoftApCapability[] newArray(int size) {
+            return new SoftApCapability[size];
+        }
+    };
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder sbuf = new StringBuilder();
+        sbuf.append("MaximumSupportedClientNumber=").append(mMaximumSupportedClientNumber);
+        return sbuf.toString();
+    }
+
+    @Override
+    public boolean equals(@NonNull Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SoftApCapability)) return false;
+        SoftApCapability capability = (SoftApCapability) o;
+        return mMaximumSupportedClientNumber == capability.mMaximumSupportedClientNumber;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mMaximumSupportedClientNumber);
+    }
+}
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index fd8a924..cd2826b 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -164,6 +164,11 @@
     private final int mChannel;
 
     /**
+     * The maximim allowed number of clients that can associate to the AP.
+     */
+    private final int mMaxNumberOfClients;
+
+    /**
      * The operating security type of the AP.
      * One of the security types from {@link @SecurityType}
      */
@@ -191,7 +196,7 @@
     /** Private constructor for Builder and Parcelable implementation. */
     private SoftApConfiguration(@Nullable String ssid, @Nullable MacAddress bssid,
             @Nullable String wpa2Passphrase, boolean hiddenSsid, @BandType int band, int channel,
-            @SecurityType int securityType) {
+            @SecurityType int securityType, int maxNumberOfClients) {
         mSsid = ssid;
         mBssid = bssid;
         mWpa2Passphrase = wpa2Passphrase;
@@ -199,6 +204,7 @@
         mBand = band;
         mChannel = channel;
         mSecurityType = securityType;
+        mMaxNumberOfClients = maxNumberOfClients;
     }
 
     @Override
@@ -216,13 +222,14 @@
                 && mHiddenSsid == other.mHiddenSsid
                 && mBand == other.mBand
                 && mChannel == other.mChannel
-                && mSecurityType == other.mSecurityType;
+                && mSecurityType == other.mSecurityType
+                && mMaxNumberOfClients == other.mMaxNumberOfClients;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mSsid, mBssid, mWpa2Passphrase, mHiddenSsid,
-                mBand, mChannel, mSecurityType);
+                mBand, mChannel, mSecurityType, mMaxNumberOfClients);
     }
 
     @Override
@@ -236,6 +243,7 @@
         sbuf.append(" \n Band =").append(mBand);
         sbuf.append(" \n Channel =").append(mChannel);
         sbuf.append(" \n SecurityType=").append(getSecurityType());
+        sbuf.append(" \n MaxClient=").append(mMaxNumberOfClients);
         return sbuf.toString();
     }
 
@@ -248,6 +256,7 @@
         dest.writeInt(mBand);
         dest.writeInt(mChannel);
         dest.writeInt(mSecurityType);
+        dest.writeInt(mMaxNumberOfClients);
     }
 
     @Override
@@ -262,7 +271,8 @@
             return new SoftApConfiguration(
                     in.readString(),
                     in.readParcelable(MacAddress.class.getClassLoader()),
-                    in.readString(), in.readBoolean(), in.readInt(), in.readInt(), in.readInt());
+                    in.readString(), in.readBoolean(), in.readInt(), in.readInt(), in.readInt(),
+                    in.readInt());
         }
 
         @Override
@@ -282,7 +292,7 @@
 
     /**
      * Returns MAC address set to be BSSID for the AP.
-     * {@link #setBssid(MacAddress)}.
+     * {@link Builder#setBssid(MacAddress)}.
      */
     @Nullable
     public MacAddress getBssid() {
@@ -291,7 +301,7 @@
 
     /**
      * Returns String set to be passphrase for the WPA2-PSK AP.
-     * {@link #setWpa2Passphrase(String)}.
+     * {@link Builder#setWpa2Passphrase(String)}.
      */
     @Nullable
     public String getWpa2Passphrase() {
@@ -301,7 +311,7 @@
     /**
      * Returns Boolean set to be indicate hidden (true: doesn't broadcast its SSID) or
      * not (false: broadcasts its SSID) for the AP.
-     * {@link #setHiddenSsid(boolean)}.
+     * {@link Builder#setHiddenSsid(boolean)}.
      */
     public boolean isHiddenSsid() {
         return mHiddenSsid;
@@ -309,7 +319,7 @@
 
     /**
      * Returns {@link BandType} set to be the band for the AP.
-     * {@link #setBand(@BandType int)}.
+     * {@link Builder#setBand(@BandType int)}.
      */
     public @BandType int getBand() {
         return mBand;
@@ -317,7 +327,7 @@
 
     /**
      * Returns Integer set to be the channel for the AP.
-     * {@link #setChannel(int)}.
+     * {@link Builder#setChannel(int)}.
      */
     public int getChannel() {
         return mChannel;
@@ -333,6 +343,14 @@
     }
 
     /**
+     * Returns the maximum number of clients that can associate to the AP.
+     * {@link Builder#setMaxNumberOfClients(int)}.
+     */
+    public int getMaxNumberOfClients() {
+        return mMaxNumberOfClients;
+    }
+
+    /**
      * Builds a {@link SoftApConfiguration}, which allows an app to configure various aspects of a
      * Soft AP.
      *
@@ -346,6 +364,7 @@
         private boolean mHiddenSsid;
         private int mBand;
         private int mChannel;
+        private int mMaxNumberOfClients;
 
         private int setSecurityType() {
             int securityType = SECURITY_TYPE_OPEN;
@@ -369,6 +388,7 @@
             mHiddenSsid = false;
             mBand = BAND_2GHZ;
             mChannel = 0;
+            mMaxNumberOfClients = 0;
         }
 
         /**
@@ -383,6 +403,7 @@
             mHiddenSsid = other.mHiddenSsid;
             mBand = other.mBand;
             mChannel = other.mChannel;
+            mMaxNumberOfClients = other.mMaxNumberOfClients;
         }
 
         /**
@@ -393,7 +414,7 @@
         @NonNull
         public SoftApConfiguration build() {
             return new SoftApConfiguration(mSsid, mBssid, mWpa2Passphrase,
-                mHiddenSsid, mBand, mChannel, setSecurityType());
+                mHiddenSsid, mBand, mChannel, setSecurityType(), mMaxNumberOfClients);
         }
 
         /**
@@ -523,5 +544,37 @@
             mChannel = channel;
             return this;
         }
+
+        /**
+         * Specifies the maximum number of clients that can associate to the AP.
+         *
+         * The maximum number of clients (STAs) which can associate to the AP.
+         * The AP will reject association from any clients above this number.
+         * Specify a value of 0 to have the framework automatically use the maximum number
+         * which the device can support (based on hardware and carrier constraints).
+         * <p>
+         * Use {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
+         * {@link SoftApCapability#getMaxSupportedClients} to get the maximum number of clients
+         * which the device supports (based on hardware and carrier constraints).
+         *
+         * <p>
+         * <li>If not set, defaults to 0.</li>
+         *
+         * This method requires hardware support. If the method is used to set a
+         * non-zero {@code maxNumberOfClients} value then
+         * {@link WifiManager#startTetheredHotspot} will report error code
+         * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
+         *
+         * @param maxNumberOfClients maximum client number of the AP.
+         * @return Builder for chaining.
+         */
+        @NonNull
+        public Builder setMaxNumberOfClients(int maxNumberOfClients) {
+            if (maxNumberOfClients < 0) {
+                throw new IllegalArgumentException("maxNumberOfClients should be not negative");
+            }
+            mMaxNumberOfClients = maxNumberOfClients;
+            return this;
+        }
     }
 }
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 5ab0583..7693f9a 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -549,6 +549,7 @@
      * currently support general and no_channel
      * @see #SAP_START_FAILURE_GENERAL
      * @see #SAP_START_FAILURE_NO_CHANNEL
+     * @see #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION
      *
      * @hide
      */
@@ -652,6 +653,7 @@
     @IntDef(flag = false, prefix = { "SAP_START_FAILURE_" }, value = {
         SAP_START_FAILURE_GENERAL,
         SAP_START_FAILURE_NO_CHANNEL,
+        SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SapStartFailure {}
@@ -673,6 +675,15 @@
     @SystemApi
     public static final int SAP_START_FAILURE_NO_CHANNEL = 1;
 
+    /**
+     *  If Wi-Fi AP start failed, this reason code means that the specified configuration
+     *  is not supported by the current HAL version.
+     *
+     *  @hide
+     */
+    @SystemApi
+    public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"IFACE_IP_MODE_"}, value = {
@@ -3431,6 +3442,16 @@
         default void onInfoChanged(@NonNull SoftApInfo softApInfo) {
             // Do nothing: can be updated to add SoftApInfo details (e.g. channel) to the UI.
         }
+
+        /**
+         * Called when capability of softap changes.
+         *
+         * @param softApCapability is the softap capability. {@link SoftApCapability}
+         */
+        default void onCapabilityChanged(@NonNull SoftApCapability softApCapability) {
+            // Do nothing: can be updated to add SoftApCapability details (e.g. meximum supported
+            // client number) to the UI.
+        }
     }
 
     /**
@@ -3484,6 +3505,19 @@
                 mCallback.onInfoChanged(softApInfo);
             });
         }
+
+        @Override
+        public void onCapabilityChanged(SoftApCapability capability) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "SoftApCallbackProxy: onCapabilityChanged: SoftApCapability="
+                        + capability);
+            }
+
+            Binder.clearCallingIdentity();
+            mExecutor.execute(() -> {
+                mCallback.onCapabilityChanged(capability);
+            });
+        }
     }
 
     /**
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 8fedda4..8badcc0 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -724,21 +724,6 @@
         public int min24GHzRssi;
         /** Minimum 6GHz RSSI for a BSSID to be considered */
         public int min6GHzRssi;
-        /** Maximum score that a network can have before bonuses */
-        public int initialScoreMax;
-        /**
-         *  Only report when there is a network's score this much higher
-         *  than the current connection.
-         */
-        public int currentConnectionBonus;
-        /** score bonus for all networks with the same network flag */
-        public int sameNetworkBonus;
-        /** score bonus for networks that are not open */
-        public int secureBonus;
-        /** 5GHz RSSI score bonus (applied to all 5GHz networks) */
-        public int band5GHzBonus;
-        /** 6GHz RSSI score bonus (applied to all 5GHz networks) */
-        public int band6GHzBonus;
         /** Pno Network filter list */
         public PnoNetwork[] networkList;
 
@@ -753,12 +738,6 @@
             dest.writeInt(min5GHzRssi);
             dest.writeInt(min24GHzRssi);
             dest.writeInt(min6GHzRssi);
-            dest.writeInt(initialScoreMax);
-            dest.writeInt(currentConnectionBonus);
-            dest.writeInt(sameNetworkBonus);
-            dest.writeInt(secureBonus);
-            dest.writeInt(band5GHzBonus);
-            dest.writeInt(band6GHzBonus);
             if (networkList != null) {
                 dest.writeInt(networkList.length);
                 for (int i = 0; i < networkList.length; i++) {
@@ -781,12 +760,6 @@
                         settings.min5GHzRssi = in.readInt();
                         settings.min24GHzRssi = in.readInt();
                         settings.min6GHzRssi = in.readInt();
-                        settings.initialScoreMax = in.readInt();
-                        settings.currentConnectionBonus = in.readInt();
-                        settings.sameNetworkBonus = in.readInt();
-                        settings.secureBonus = in.readInt();
-                        settings.band5GHzBonus = in.readInt();
-                        settings.band6GHzBonus = in.readInt();
                         int numNetworks = in.readInt();
                         settings.networkList = new PnoNetwork[numNetworks];
                         for (int i = 0; i < numNetworks; i++) {
diff --git a/wifi/java/android/net/wifi/wificond/SingleScanSettings.java b/wifi/java/android/net/wifi/wificond/SingleScanSettings.java
index 8065c01..8c341b8 100644
--- a/wifi/java/android/net/wifi/wificond/SingleScanSettings.java
+++ b/wifi/java/android/net/wifi/wificond/SingleScanSettings.java
@@ -16,7 +16,6 @@
 
 package android.net.wifi.wificond;
 
-import android.net.wifi.IWifiScannerImpl;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Log;
diff --git a/wifi/java/android/net/wifi/wificond/WifiCondManager.java b/wifi/java/android/net/wifi/wificond/WifiCondManager.java
index 94f1212..283f2dd 100644
--- a/wifi/java/android/net/wifi/wificond/WifiCondManager.java
+++ b/wifi/java/android/net/wifi/wificond/WifiCondManager.java
@@ -24,14 +24,6 @@
 import android.annotation.SystemService;
 import android.app.AlarmManager;
 import android.content.Context;
-import android.net.wifi.IApInterface;
-import android.net.wifi.IApInterfaceEventCallback;
-import android.net.wifi.IClientInterface;
-import android.net.wifi.IPnoScanEvent;
-import android.net.wifi.IScanEvent;
-import android.net.wifi.ISendMgmtFrameEvent;
-import android.net.wifi.IWifiScannerImpl;
-import android.net.wifi.IWificond;
 import android.net.wifi.SoftApInfo;
 import android.net.wifi.WifiAnnotations;
 import android.net.wifi.WifiScanner;
diff --git a/wifi/tests/src/android/net/wifi/SoftApCapabilityTest.java b/wifi/tests/src/android/net/wifi/SoftApCapabilityTest.java
new file mode 100644
index 0000000..9cb221d
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/SoftApCapabilityTest.java
@@ -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 android.net.wifi;
+
+import android.os.Parcel;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.SoftApCapability}.
+ */
+@SmallTest
+public class SoftApCapabilityTest {
+
+    /**
+     * Verifies copy constructor.
+     */
+    @Test
+    public void testCopyOperator() throws Exception {
+        SoftApCapability capability = new SoftApCapability();
+        capability.setMaxSupportedClients(10);
+
+        SoftApCapability copiedCapability = new SoftApCapability(capability);
+
+        assertEquals(capability, copiedCapability);
+        assertEquals(capability.hashCode(), copiedCapability.hashCode());
+    }
+
+    /**
+     * Verifies parcel serialization/deserialization.
+     */
+    @Test
+    public void testParcelOperation() throws Exception {
+        SoftApCapability capability = new SoftApCapability();
+        capability.setMaxSupportedClients(10);
+
+        Parcel parcelW = Parcel.obtain();
+        capability.writeToParcel(parcelW, 0);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        SoftApCapability fromParcel = SoftApCapability.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(capability, fromParcel);
+        assertEquals(capability.hashCode(), fromParcel.hashCode());
+    }
+
+}
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index 60125e3..1f60103 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -50,6 +50,7 @@
         assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_2GHZ);
         assertThat(original.getChannel()).isEqualTo(0);
         assertThat(original.isHiddenSsid()).isEqualTo(false);
+        assertThat(original.getMaxNumberOfClients()).isEqualTo(0);
 
         SoftApConfiguration unparceled = parcelUnparcel(original);
         assertThat(unparceled).isNotSameAs(original);
@@ -73,7 +74,7 @@
         assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_2GHZ);
         assertThat(original.getChannel()).isEqualTo(0);
         assertThat(original.isHiddenSsid()).isEqualTo(false);
-
+        assertThat(original.getMaxNumberOfClients()).isEqualTo(0);
 
         SoftApConfiguration unparceled = parcelUnparcel(original);
         assertThat(unparceled).isNotSameAs(original);
@@ -87,12 +88,13 @@
     }
 
     @Test
-    public void testWpa2WithBandAndChannelAndHiddenNetwork() {
+    public void testWpa2WithAllFieldCustomized() {
         SoftApConfiguration original = new SoftApConfiguration.Builder()
                 .setWpa2Passphrase("secretsecret")
                 .setBand(SoftApConfiguration.BAND_ANY)
                 .setChannel(149, SoftApConfiguration.BAND_5GHZ)
                 .setHiddenSsid(true)
+                .setMaxNumberOfClients(10)
                 .build();
         assertThat(original.getWpa2Passphrase()).isEqualTo("secretsecret");
         assertThat(original.getSecurityType()).isEqualTo(
@@ -100,7 +102,7 @@
         assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ);
         assertThat(original.getChannel()).isEqualTo(149);
         assertThat(original.isHiddenSsid()).isEqualTo(true);
-
+        assertThat(original.getMaxNumberOfClients()).isEqualTo(10);
 
         SoftApConfiguration unparceled = parcelUnparcel(original);
         assertThat(unparceled).isNotSameAs(original);
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 8216611..d8bc134 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -877,6 +877,25 @@
         verify(mSoftApCallback).onInfoChanged(testSoftApInfo);
     }
 
+
+    /*
+     * Verify client-provided callback is being called through callback proxy
+     */
+    @Test
+    public void softApCallbackProxyCallsOnCapabilityChanged() throws Exception {
+        SoftApCapability testSoftApCapability = new SoftApCapability();
+        testSoftApCapability.setMaxSupportedClients(10);
+        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().onCapabilityChanged(testSoftApCapability);
+        mLooper.dispatchAll();
+        verify(mSoftApCallback).onCapabilityChanged(testSoftApCapability);
+    }
+
     /*
      * Verify client-provided callback is being called through callback proxy on multiple events
      */
@@ -885,6 +904,8 @@
         SoftApInfo testSoftApInfo = new SoftApInfo();
         testSoftApInfo.setFrequency(TEST_AP_FREQUENCY);
         testSoftApInfo.setBandwidth(TEST_AP_BANDWIDTH);
+        SoftApCapability testSoftApCapability = new SoftApCapability();
+        testSoftApCapability.setMaxSupportedClients(10);
         ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
                 ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
         mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
@@ -896,12 +917,15 @@
         callbackCaptor.getValue().onConnectedClientsChanged(testClients);
         callbackCaptor.getValue().onInfoChanged(testSoftApInfo);
         callbackCaptor.getValue().onStateChanged(WIFI_AP_STATE_FAILED, SAP_START_FAILURE_GENERAL);
+        callbackCaptor.getValue().onCapabilityChanged(testSoftApCapability);
+
 
         mLooper.dispatchAll();
         verify(mSoftApCallback).onStateChanged(WIFI_AP_STATE_ENABLING, 0);
         verify(mSoftApCallback).onConnectedClientsChanged(testClients);
         verify(mSoftApCallback).onInfoChanged(testSoftApInfo);
         verify(mSoftApCallback).onStateChanged(WIFI_AP_STATE_FAILED, SAP_START_FAILURE_GENERAL);
+        verify(mSoftApCallback).onCapabilityChanged(testSoftApCapability);
     }
 
     /*
diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
index fa4f711..1af0bcb 100644
--- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
@@ -80,12 +80,6 @@
     private static final int TEST_PNOSETTINGS_MIN_5GHZ_RSSI = -60;
     private static final int TEST_PNOSETTINGS_MIN_2GHZ_RSSI = -70;
     private static final int TEST_PNOSETTINGS_MIN_6GHZ_RSSI = -55;
-    private static final int TEST_PNOSETTINGS_INITIAL_SCORE_MAX = 50;
-    private static final int TEST_PNOSETTINGS_CURRENT_CONNECTION_BONUS = 10;
-    private static final int TEST_PNOSETTINGS_SAME_NETWORK_BONUS = 11;
-    private static final int TEST_PNOSETTINGS_SECURE_BONUS = 12;
-    private static final int TEST_PNOSETTINGS_BAND_5GHZ_BONUS = 13;
-    private static final int TEST_PNOSETTINGS_BAND_6GHZ_BONUS = 15;
     private static final String TEST_SSID_1 = "TEST1";
     private static final String TEST_SSID_2 = "TEST2";
     private static final int[] TEST_FREQUENCIES_1 = {};
@@ -186,12 +180,6 @@
         pnoSettings.min5GHzRssi = TEST_PNOSETTINGS_MIN_5GHZ_RSSI;
         pnoSettings.min24GHzRssi = TEST_PNOSETTINGS_MIN_2GHZ_RSSI;
         pnoSettings.min6GHzRssi = TEST_PNOSETTINGS_MIN_6GHZ_RSSI;
-        pnoSettings.initialScoreMax = TEST_PNOSETTINGS_INITIAL_SCORE_MAX;
-        pnoSettings.currentConnectionBonus = TEST_PNOSETTINGS_CURRENT_CONNECTION_BONUS;
-        pnoSettings.sameNetworkBonus = TEST_PNOSETTINGS_SAME_NETWORK_BONUS;
-        pnoSettings.secureBonus = TEST_PNOSETTINGS_SECURE_BONUS;
-        pnoSettings.band5GHzBonus = TEST_PNOSETTINGS_BAND_5GHZ_BONUS;
-        pnoSettings.band6GHzBonus = TEST_PNOSETTINGS_BAND_6GHZ_BONUS;
 
         Parcel parcel = Parcel.obtain();
         pnoSettings.writeToParcel(parcel, 0);
@@ -205,14 +193,6 @@
         assertEquals(TEST_PNOSETTINGS_MIN_5GHZ_RSSI, pnoSettingsDeserialized.min5GHzRssi);
         assertEquals(TEST_PNOSETTINGS_MIN_2GHZ_RSSI, pnoSettingsDeserialized.min24GHzRssi);
         assertEquals(TEST_PNOSETTINGS_MIN_6GHZ_RSSI, pnoSettingsDeserialized.min6GHzRssi);
-        assertEquals(TEST_PNOSETTINGS_INITIAL_SCORE_MAX, pnoSettingsDeserialized.initialScoreMax);
-        assertEquals(TEST_PNOSETTINGS_CURRENT_CONNECTION_BONUS,
-                pnoSettingsDeserialized.currentConnectionBonus);
-        assertEquals(TEST_PNOSETTINGS_SAME_NETWORK_BONUS,
-                pnoSettingsDeserialized.sameNetworkBonus);
-        assertEquals(TEST_PNOSETTINGS_SECURE_BONUS, pnoSettingsDeserialized.secureBonus);
-        assertEquals(TEST_PNOSETTINGS_BAND_5GHZ_BONUS, pnoSettingsDeserialized.band5GHzBonus);
-        assertEquals(TEST_PNOSETTINGS_BAND_6GHZ_BONUS, pnoSettingsDeserialized.band6GHzBonus);
 
         // Test parsing of PnoNetwork
         assertEquals(pnoSettings.networkList.length, pnoSettingsDeserialized.networkList.length);
diff --git a/wifi/tests/src/android/net/wifi/wificond/SingleScanSettingsTest.java b/wifi/tests/src/android/net/wifi/wificond/SingleScanSettingsTest.java
index ef59839..f20ec47 100644
--- a/wifi/tests/src/android/net/wifi/wificond/SingleScanSettingsTest.java
+++ b/wifi/tests/src/android/net/wifi/wificond/SingleScanSettingsTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertEquals;
 
-import android.net.wifi.IWifiScannerImpl;
 import android.os.Parcel;
 
 import androidx.test.filters.SmallTest;
diff --git a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
index 68e5336..f3867c1 100644
--- a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
@@ -38,14 +38,6 @@
 import android.app.AlarmManager;
 import android.app.test.TestAlarmManager;
 import android.content.Context;
-import android.net.wifi.IApInterface;
-import android.net.wifi.IApInterfaceEventCallback;
-import android.net.wifi.IClientInterface;
-import android.net.wifi.IPnoScanEvent;
-import android.net.wifi.IScanEvent;
-import android.net.wifi.ISendMgmtFrameEvent;
-import android.net.wifi.IWifiScannerImpl;
-import android.net.wifi.IWificond;
 import android.net.wifi.SoftApInfo;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiScanner;