Merge "Add new Factory reset protection policy APIs"
diff --git a/Android.bp b/Android.bp
index bba6185..8a93fdb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -262,12 +262,13 @@
     name: "framework-updatable-sources",
     srcs: [
         ":framework-appsearch-sources",
-        ":framework-sdkext-sources",
+        ":framework-sdkextensions-sources",
         ":framework-statsd-sources",
         ":framework-tethering-srcs",
         ":updatable-media-srcs",
         ":framework-mediaprovider-sources",
         ":framework-wifi-updatable-sources",
+        ":ike-srcs",
     ]
 }
 
@@ -347,7 +348,6 @@
         "android.hardware.vibrator-V1.1-java",
         "android.hardware.vibrator-V1.2-java",
         "android.hardware.vibrator-V1.3-java",
-        "android.hardware.wifi-V1.0-java-constants",
         "devicepolicyprotosnano",
 
         "com.android.sysprop.apex",
@@ -415,6 +415,13 @@
 }
 
 filegroup {
+    name: "graphicsstats_proto",
+    srcs: [
+        "libs/hwui/protos/graphicsstats.proto",
+    ],
+}
+
+filegroup {
     name: "libvibrator_aidl",
     srcs: [
         "core/java/android/os/IExternalVibrationController.aidl",
@@ -432,6 +439,7 @@
         // TODO(b/146167933): Use framework-statsd-stubs
         "framework-statsd",
         "framework-wifi-stubs",
+        "ike-stubs",
     ],
     installable: true,
     javac_shard_size: 150,
@@ -440,8 +448,6 @@
         "libcore-platform-compat-config",
         "services-platform-compat-config",
         "media-provider-platform-compat-config",
-        "services-devicepolicy-platform-compat-config",
-        "services-core-platform-compat-config",
     ],
     static_libs: [
         // If MimeMap ever becomes its own APEX, then this dependency would need to be removed
@@ -480,13 +486,15 @@
         "framework-minus-apex",
         "updatable_media_stubs",
         "framework_mediaprovider_stubs",
-        "framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs
-        "framework-sdkext-stubs-systemapi",
+        "framework-appsearch-stubs",
+        "framework-sdkextensions-stubs-systemapi",
         // TODO(b/146167933): Use framework-statsd-stubs instead.
         "framework-statsd",
         // TODO(b/140299412): should be framework-wifi-stubs
         "framework-wifi",
-        // TODO(jiyong): add more stubs for APEXes here
+        "ike-stubs",
+        // TODO(b/147200698): should be the stub of framework-tethering
+        "framework-tethering",
     ],
     sdk_version: "core_platform",
     apex_available: ["//apex_available:platform"],
@@ -497,7 +505,10 @@
     defaults: ["framework-defaults"],
     srcs: [":framework-all-sources"],
     installable: false,
-    static_libs: ["exoplayer2-core"],
+    static_libs: [
+        "exoplayer2-core",
+        "android.hardware.wifi-V1.0-java-constants",
+     ],
     apex_available: ["//apex_available:platform"],
 }
 
@@ -595,17 +606,22 @@
 filegroup {
     name: "framework-annotations",
     srcs: [
+        "core/java/android/annotation/CallbackExecutor.java",
+        "core/java/android/annotation/CheckResult.java",
         "core/java/android/annotation/IntDef.java",
         "core/java/android/annotation/IntRange.java",
         "core/java/android/annotation/NonNull.java",
         "core/java/android/annotation/Nullable.java",
         "core/java/android/annotation/RequiresPermission.java",
         "core/java/android/annotation/SdkConstant.java",
+        "core/java/android/annotation/StringDef.java",
         "core/java/android/annotation/SystemApi.java",
+        "core/java/android/annotation/SystemService.java",
         "core/java/android/annotation/TestApi.java",
         "core/java/android/annotation/UnsupportedAppUsage.java",
         "core/java/com/android/internal/annotations/GuardedBy.java",
         "core/java/com/android/internal/annotations/VisibleForTesting.java",
+        "core/java/com/android/internal/annotations/Immutable.java",
     ],
 }
 
@@ -616,6 +632,15 @@
 }
 
 filegroup {
+    name: "framework-ike-shared-srcs",
+    visibility: ["//frameworks/opt/net/ike"],
+    srcs: [
+        "core/java/android/net/annotations/PolicyDirection.java",
+        "telephony/java/android/telephony/Annotation.java",
+    ],
+}
+
+filegroup {
     name: "framework-networkstack-shared-srcs",
     srcs: [
         // TODO: remove these annotations as soon as we can use andoid.support.annotations.*
@@ -648,6 +673,7 @@
         "core/java/com/android/internal/util/Preconditions.java",
         "core/java/com/android/internal/util/State.java",
         "core/java/com/android/internal/util/StateMachine.java",
+        "core/java/com/android/internal/util/TrafficStatsConstants.java",
         "core/java/android/net/shared/Inet4AddressUtils.java",
     ],
 }
@@ -1129,12 +1155,36 @@
     ],
 }
 
+// utility classes statically linked into framework-wifi and dynamically linked
+// into wifi-service
+java_library {
+    name: "framework-wifi-util-lib",
+    sdk_version: "core_current",
+    srcs: [
+        "core/java/android/content/pm/BaseParceledListSlice.java",
+        "core/java/android/content/pm/ParceledListSlice.java",
+        "core/java/android/net/shared/Inet4AddressUtils.java",
+        "core/java/android/os/HandlerExecutor.java",
+        "core/java/com/android/internal/util/AsyncChannel.java",
+        "core/java/com/android/internal/util/AsyncService.java",
+        "core/java/com/android/internal/util/Protocol.java",
+        "core/java/com/android/internal/util/Preconditions.java",
+        "telephony/java/android/telephony/Annotation.java",
+    ],
+    libs: [
+        "framework-annotations-lib",
+        "unsupportedappusage",
+        "android_system_stubs_current",
+    ],
+    visibility: ["//frameworks/base/wifi"],
+}
+
+// utility classes statically linked into wifi-service
 filegroup {
     name: "framework-wifi-service-shared-srcs",
     srcs: [
-        ":framework-annotations",
         "core/java/android/net/InterfaceConfiguration.java",
-        "core/java/android/os/HandlerExecutor.java",
+        "core/java/android/os/BasicShellCommandHandler.java",
         "core/java/android/util/BackupUtils.java",
         "core/java/android/util/LocalLog.java",
         "core/java/android/util/Rational.java",
@@ -1142,7 +1192,6 @@
         "core/java/com/android/internal/util/HexDump.java",
         "core/java/com/android/internal/util/IState.java",
         "core/java/com/android/internal/util/MessageUtils.java",
-        "core/java/com/android/internal/util/Preconditions.java",
         "core/java/com/android/internal/util/State.java",
         "core/java/com/android/internal/util/StateMachine.java",
         "core/java/com/android/internal/util/WakeupMessage.java",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index d195047..baa3c61 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -41,10 +41,9 @@
 ]
 
 stubs_defaults {
-    name: "metalava-api-stubs-default",
+    name: "metalava-non-updatable-api-stubs-default",
     srcs: [
         ":framework-non-updatable-sources",
-        ":framework-updatable-sources",
         "core/java/**/*.logtags",
         ":opt-telephony-srcs",
         ":opt-net-voip-srcs",
@@ -64,14 +63,23 @@
         "sdk-dir",
         "api-versions-jars-dir",
     ],
-    sdk_version: "core_platform",
     filter_packages: packages_to_document,
 }
 
+stubs_defaults {
+    name: "metalava-api-stubs-default",
+    defaults: ["metalava-non-updatable-api-stubs-default"],
+    srcs: [":framework-updatable-sources"],
+    sdk_version: "core_platform",
+}
+
 /////////////////////////////////////////////////////////////////////
 // *-api-stubs-docs modules providing source files for the stub libraries
 /////////////////////////////////////////////////////////////////////
 
+// api-stubs-docs, system-api-stubs-docs, and test-api-stubs-docs have APIs
+// from the non-updatable part of the platform as well as from the updatable
+// modules
 droidstubs {
     name: "api-stubs-docs",
     defaults: ["metalava-api-stubs-default"],
@@ -112,7 +120,10 @@
     arg_files: [
         "core/res/AndroidManifest.xml",
     ],
-    args: metalava_framework_docs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\)",
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)",
     check_api: {
         current: {
             api_file: "api/system-current.txt",
@@ -155,6 +166,111 @@
 }
 
 /////////////////////////////////////////////////////////////////////
+// Following droidstubs modules are for extra APIs for modules.
+// The framework currently have two more API surfaces for modules:
+// @SystemApi(client=MODULE_APPS) and @SystemApi(client=MODULE_LIBRARIES)
+/////////////////////////////////////////////////////////////////////
+
+// TODO(b/146727827) remove the *-api modules when we can teach metalava
+// about the relationship among the API surfaces. Currently, these modules are only to generate
+// the API signature files and ensure that the APIs evolve in a backwards compatible manner.
+// They however are NOT used for building the API stub.
+droidstubs {
+    name: "module-app-api",
+    defaults: ["metalava-non-updatable-api-stubs-default"],
+    libs: ["framework-all"],
+    arg_files: ["core/res/AndroidManifest.xml"],
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)",
+    check_api: {
+        current: {
+            api_file: "api/module-app-current.txt",
+            removed_api_file: "api/module-app-removed.txt",
+        },
+        // TODO(b/147559833) enable the compatibility check against the last release API
+        // and the API lint
+        //last_released: {
+        //    api_file: ":last-released-module-app-api",
+        //    removed_api_file: "api/module-app-removed.txt",
+        //    baseline_file: ":module-app-api-incompatibilities-with-last-released"
+        //},
+        //api_lint: {
+        //    enabled: true,
+        //    new_since: ":last-released-module-app-api",
+        //    baseline_file: "api/module-app-lint-baseline.txt",
+        //},
+    },
+    //jdiff_enabled: true,
+}
+
+droidstubs {
+    name: "module-lib-api",
+    defaults: ["metalava-non-updatable-api-stubs-default"],
+    libs: ["framework-all"],
+    arg_files: ["core/res/AndroidManifest.xml"],
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+        "process=android.annotation.SystemApi.Process.ALL\\)",
+    check_api: {
+        current: {
+            api_file: "api/module-lib-current.txt",
+            removed_api_file: "api/module-lib-removed.txt",
+        },
+        // TODO(b/147559833) enable the compatibility check against the last release API
+        // and the API lint
+        //last_released: {
+        //    api_file: ":last-released-module-lib-api",
+        //    removed_api_file: "api/module-lib-removed.txt",
+        //    baseline_file: ":module-lib-api-incompatibilities-with-last-released"
+        //},
+        //api_lint: {
+        //    enabled: true,
+        //    new_since: ":last-released-module-lib-api",
+        //    baseline_file: "api/module-lib-lint-baseline.txt",
+        //},
+    },
+    //jdiff_enabled: true,
+}
+
+// The following two droidstubs modules generate source files for the API stub libraries for
+// modules. Note that they not only include their own APIs but also other APIs that have
+// narrower scope. For example, module-lib-api-stubs-docs includes all @SystemApis not just
+// the ones with 'client=MODULE_LIBRARIES'.
+droidstubs {
+    name: "module-app-api-stubs-docs",
+    defaults: ["metalava-non-updatable-api-stubs-default"],
+    libs: ["framework-all"],
+    arg_files: ["core/res/AndroidManifest.xml"],
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)" +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)",
+}
+
+droidstubs {
+    name: "module-lib-api-stubs-docs",
+    defaults: ["metalava-non-updatable-api-stubs-default"],
+    libs: ["framework-all"],
+    arg_files: ["core/res/AndroidManifest.xml"],
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)" +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)" +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+        "process=android.annotation.SystemApi.Process.ALL\\)",
+}
+
+/////////////////////////////////////////////////////////////////////
 // android_*_stubs_current modules are the stubs libraries compiled
 // from *-api-stubs-docs
 /////////////////////////////////////////////////////////////////////
@@ -169,7 +285,6 @@
     java_resources: [
         ":notices-for-framework-stubs",
     ],
-    sdk_version: "core_current",
     system_modules: "none",
     java_version: "1.8",
     compile_dex: true,
@@ -187,6 +302,7 @@
         "private-stub-annotations-jar",
     ],
     defaults: ["framework-stubs-default"],
+    sdk_version: "core_current",
 }
 
 java_library_static {
@@ -201,6 +317,7 @@
         "private-stub-annotations-jar",
     ],
     defaults: ["framework-stubs-default"],
+    sdk_version: "core_current",
 }
 
 java_library_static {
@@ -215,6 +332,37 @@
         "private-stub-annotations-jar",
     ],
     defaults: ["framework-stubs-default"],
+    sdk_version: "core_current",
+}
+
+java_library_static {
+    name: "framework_module_app_stubs_current",
+    srcs: [
+        ":module-app-api-stubs-docs",
+    ],
+    libs: [
+        "stub-annotations",
+        "framework-all",
+    ],
+    static_libs: [
+        "private-stub-annotations-jar",
+    ],
+    defaults: ["framework-stubs-default"],
+}
+
+java_library_static {
+    name: "framework_module_lib_stubs_current",
+    srcs: [
+        ":module-lib-api-stubs-docs",
+    ],
+    libs: [
+        "stub-annotations",
+        "framework-all",
+    ],
+    static_libs: [
+        "private-stub-annotations-jar",
+    ],
+    defaults: ["framework-stubs-default"],
 }
 
 /////////////////////////////////////////////////////////////////////
diff --git a/apex/Android.bp b/apex/Android.bp
index 56f7db2..abebfa3 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -37,6 +37,36 @@
 
 stubs_defaults {
     name: "framework-module-stubs-defaults-systemapi",
-    args: mainline_stubs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) ",
+    args: mainline_stubs_args +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+    "process=android.annotation.SystemApi.Process.ALL\\) ",
+    installable: false,
+}
+
+stubs_defaults {
+    name: "framework-module-stubs-defaults-module_apps_api",
+    args: mainline_stubs_args +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+    "process=android.annotation.SystemApi.Process.ALL\\) " +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+    "process=android.annotation.SystemApi.Process.ALL\\) ",
+    installable: false,
+}
+
+stubs_defaults {
+    name: "framework-module-stubs-defaults-module_libs_api",
+    args: mainline_stubs_args +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+    "process=android.annotation.SystemApi.Process.ALL\\) " +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+    "process=android.annotation.SystemApi.Process.ALL\\) " +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+    "process=android.annotation.SystemApi.Process.ALL\\) ",
     installable: false,
 }
diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp
index 3dc5a2c..60cc3be 100644
--- a/apex/appsearch/framework/Android.bp
+++ b/apex/appsearch/framework/Android.bp
@@ -26,26 +26,14 @@
   installable: true,
   sdk_version: "core_platform", // TODO(b/146218515) should be core_current
   srcs: [":framework-appsearch-sources"],
+  hostdex: true, // for hiddenapi check
   libs: [
     "framework-minus-apex",  // TODO(b/146218515) should be framework-system-stubs
   ],
+  visibility: ["//frameworks/base/apex/appsearch:__subpackages__"],
+  apex_available: ["com.android.appsearch"],
 }
 
-metalava_appsearch_docs_args =
-    "--hide-package com.android.server " +
-    "--error UnhiddenSystemApi " +
-    "--hide RequiresPermission " +
-    "--hide MissingPermission " +
-    "--hide BroadcastBehavior " +
-    "--hide HiddenSuperclass " +
-    "--hide DeprecationMismatch " +
-    "--hide UnavailableSymbol " +
-    "--hide SdkConstant " +
-    "--hide HiddenTypeParameter " +
-    "--hide Todo --hide Typo " +
-    "--hide HiddenTypedefConstant " +
-    "--show-annotation android.annotation.SystemApi "
-
 droidstubs {
     name: "framework-appsearch-stubs-srcs",
     srcs: [
@@ -55,9 +43,8 @@
     aidl: {
         include_dirs: ["frameworks/base/core/java"],
     },
-    args: metalava_appsearch_docs_args,
-    sdk_version: "core_current",
-    libs: ["android_system_stubs_current"],
+    defaults: ["framework-module-stubs-defaults-systemapi"],
+    sdk_version: "system_current",
 }
 
 java_library {
@@ -68,7 +55,6 @@
             "java",
         ],
     },
-    sdk_version: "core_current",
-    libs: ["android_system_stubs_current"],
+    sdk_version: "system_current",
     installable: false,
 }
diff --git a/apex/appsearch/service/Android.bp b/apex/appsearch/service/Android.bp
index 4ebafce8..e7abcd9 100644
--- a/apex/appsearch/service/Android.bp
+++ b/apex/appsearch/service/Android.bp
@@ -20,8 +20,10 @@
   libs: [
     "framework",
     "services.core",
+    "framework-appsearch",
   ],
   static_libs: [
     "icing-java-proto-lite",
-  ]
+  ],
+  apex_available: [ "com.android.appsearch" ],
 }
diff --git a/apex/blobstore/OWNERS b/apex/blobstore/OWNERS
new file mode 100644
index 0000000..8e04399
--- /dev/null
+++ b/apex/blobstore/OWNERS
@@ -0,0 +1,4 @@
+set noparent
+
+sudheersai@google.com
+yamasani@google.com
diff --git a/apex/blobstore/TEST_MAPPING b/apex/blobstore/TEST_MAPPING
new file mode 100644
index 0000000..4dc0c49
--- /dev/null
+++ b/apex/blobstore/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsBlobStoreTestCases"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/apex/jobscheduler/framework/Android.bp b/apex/jobscheduler/framework/Android.bp
index 98bbe82..ec07426 100644
--- a/apex/jobscheduler/framework/Android.bp
+++ b/apex/jobscheduler/framework/Android.bp
@@ -25,5 +25,6 @@
     },
     libs: [
         "framework-minus-apex",
+        "unsupportedappusage",
     ],
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 8b3b3a2..0bb07ca 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -29,7 +29,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ClipData;
 import android.content.ComponentName;
 import android.net.NetworkRequest;
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 42cf17b..ef1351e 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -18,8 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
-import android.app.job.IJobCallback;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ClipData;
 import android.net.Network;
 import android.net.Uri;
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java b/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
index c6631fa..0c45cbf 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
@@ -19,7 +19,7 @@
 import static android.app.job.JobInfo.NETWORK_BYTES_UNKNOWN;
 
 import android.annotation.BytesLong;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Intent;
 import android.os.Build;
 import android.os.Parcel;
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 041825c..6109b71 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -1,5 +1,6 @@
 package com.android.server.usage;
 
+import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.usage.AppStandbyInfo;
 import android.app.usage.UsageEvents;
@@ -99,8 +100,18 @@
 
     List<AppStandbyInfo> getAppStandbyBuckets(int userId);
 
-    void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
-            int reason, long elapsedRealtime, boolean resetTimeout);
+    /**
+     * Changes an app's standby bucket to the provided value. The caller can only set the standby
+     * bucket for a different app than itself.
+     */
+    void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId, int callingUid,
+            int callingPid);
+
+    /**
+     * Changes the app standby bucket for multiple apps at once.
+     */
+    void setAppStandbyBuckets(@NonNull List<AppStandbyInfo> appBuckets, int userId, int callingUid,
+            int callingPid);
 
     void addActiveDeviceAdmin(String adminPkg, int userId);
 
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 3f58c72..f8b2f32 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -761,6 +761,7 @@
         @Override
         public void onTrigger(TriggerEvent event) {
             synchronized (DeviceIdleController.this) {
+                // One_shot sensors (which call onTrigger) are unregistered when onTrigger is called
                 active = false;
                 motionLocked();
             }
@@ -769,6 +770,9 @@
         @Override
         public void onSensorChanged(SensorEvent event) {
             synchronized (DeviceIdleController.this) {
+                // Since one_shot sensors are unregistered when onTrigger is called, unregister
+                // listeners here so that the MotionListener is in a consistent state when it calls
+                // out to motionLocked.
                 mSensorManager.unregisterListener(this, mMotionSensor);
                 active = false;
                 motionLocked();
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 2f8b513..58eb589 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -47,6 +47,7 @@
 
 import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 
+import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
@@ -101,7 +102,6 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.usage.AppIdleHistory.AppUsageHistory;
-import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
 
 import java.io.File;
 import java.io.PrintWriter;
@@ -109,6 +109,7 @@
 import java.time.format.DateTimeParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
@@ -1014,14 +1015,57 @@
         }
     }
 
-    @VisibleForTesting
-    void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
-            int reason, long elapsedRealtime) {
-        setAppStandbyBucket(packageName, userId, newBucket, reason, elapsedRealtime, false);
+    @Override
+    public void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId,
+            int callingUid, int callingPid) {
+        setAppStandbyBuckets(
+                Collections.singletonList(new AppStandbyInfo(packageName, bucket)),
+                userId, callingUid, callingPid);
     }
 
     @Override
-    public void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
+    public void setAppStandbyBuckets(@NonNull List<AppStandbyInfo> appBuckets, int userId,
+            int callingUid, int callingPid) {
+        userId = ActivityManager.handleIncomingUser(
+                callingPid, callingUid, userId, false, true, "setAppStandbyBucket", null);
+        final boolean shellCaller = callingUid == Process.ROOT_UID
+                || callingUid == Process.SHELL_UID;
+        final boolean systemCaller = UserHandle.isCore(callingUid);
+        final int reason = systemCaller ? REASON_MAIN_FORCED : REASON_MAIN_PREDICTED;
+        final int packageFlags = PackageManager.MATCH_ANY_USER
+                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                | PackageManager.MATCH_DIRECT_BOOT_AWARE;
+        final int numApps = appBuckets.size();
+        final long elapsedRealtime = mInjector.elapsedRealtime();
+        for (int i = 0; i < numApps; ++i) {
+            final AppStandbyInfo bucketInfo = appBuckets.get(i);
+            final String packageName = bucketInfo.mPackageName;
+            final int bucket = bucketInfo.mStandbyBucket;
+            if (bucket < STANDBY_BUCKET_ACTIVE || bucket > STANDBY_BUCKET_NEVER) {
+                throw new IllegalArgumentException("Cannot set the standby bucket to " + bucket);
+            }
+            final int packageUid = mInjector.getPackageManagerInternal()
+                    .getPackageUid(packageName, packageFlags, userId);
+            // Caller cannot set their own standby state
+            if (packageUid == callingUid) {
+                throw new IllegalArgumentException("Cannot set your own standby bucket");
+            }
+            if (packageUid < 0) {
+                throw new IllegalArgumentException(
+                        "Cannot set standby bucket for non existent package (" + packageName + ")");
+            }
+            setAppStandbyBucket(packageName, userId, bucket, reason, elapsedRealtime, shellCaller);
+        }
+    }
+
+    @VisibleForTesting
+    void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
+            int reason) {
+        setAppStandbyBucket(
+                packageName, userId, newBucket, reason, mInjector.elapsedRealtime(), false);
+    }
+
+    private void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
             int reason, long elapsedRealtime, boolean resetTimeout) {
         synchronized (mAppIdleLock) {
             // If the package is not installed, don't allow the bucket to be set.
@@ -1444,6 +1488,10 @@
             mBatteryStats.noteEvent(event, packageName, uid);
         }
 
+        PackageManagerInternal getPackageManagerInternal() {
+            return mPackageManagerInternal;
+        }
+
         boolean isPackageEphemeral(int userId, String packageName) {
             return mPackageManagerInternal.isPackageEphemeral(userId, packageName);
         }
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index 6bd0086..18382a4 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -55,6 +55,13 @@
     jarjar_rules: "jarjar_rules.txt",
 
     plugins: ["java_api_finder"],
+
+    hostdex: true, // for hiddenapi check
+    visibility: ["//frameworks/av/apex:__subpackages__"],
+    apex_available: [
+        "com.android.media",
+        "test_com.android.media",
+    ],
 }
 
 filegroup {
diff --git a/apex/media/framework/java/android/media/MediaController2.java b/apex/media/framework/java/android/media/MediaController2.java
index c3dd3fe..d059c67 100644
--- a/apex/media/framework/java/android/media/MediaController2.java
+++ b/apex/media/framework/java/android/media/MediaController2.java
@@ -141,6 +141,9 @@
                 // Note: unbindService() throws IllegalArgumentException when it's called twice.
                 return;
             }
+            if (DEBUG) {
+                Log.d(TAG, "closing " + this);
+            }
             mClosed = true;
             if (mServiceConnection != null) {
                 // Note: This should be called even when the bindService() has returned false.
diff --git a/apex/sdkext/TEST_MAPPING b/apex/sdkext/TEST_MAPPING
deleted file mode 100644
index 91947f3..0000000
--- a/apex/sdkext/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "CtsSdkExtTestCases"
-    }
-  ]
-}
diff --git a/apex/sdkext/framework/Android.bp b/apex/sdkext/framework/Android.bp
deleted file mode 100644
index a50dc3d..0000000
--- a/apex/sdkext/framework/Android.bp
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_visibility: [ ":__pkg__" ]
-}
-
-filegroup {
-    name: "framework-sdkext-sources",
-    srcs: [
-        "java/**/*.java",
-    ],
-    path: "java",
-    visibility: [ "//frameworks/base:__pkg__" ] // For the "global" stubs.
-}
-
-java_library {
-    name: "framework-sdkext",
-    srcs: [ ":framework-sdkext-sources" ],
-    sdk_version: "system_current",
-    libs: [ "framework-annotations-lib" ],
-    permitted_packages: [ "android.os.ext" ],
-    installable: true,
-    visibility: [ "//frameworks/base/apex/sdkext:__pkg__" ],
-}
-
-droidstubs {
-    name: "framework-sdkext-droidstubs-publicapi",
-    defaults: [
-        "framework-sdkext-stubs-defaults",
-        "framework-module-stubs-defaults-publicapi",
-    ]
-}
-
-droidstubs {
-    name: "framework-sdkext-droidstubs-systemapi",
-    defaults: [
-        "framework-sdkext-stubs-defaults",
-        "framework-module-stubs-defaults-systemapi",
-    ]
-}
-
-stubs_defaults {
-    name: "framework-sdkext-stubs-defaults",
-    srcs: [
-        ":framework-sdkext-sources",
-        ":framework-annotations",
-    ],
-    sdk_version: "system_current",
-}
-
-java_library {
-    name: "framework-sdkext-stubs-systemapi",
-    srcs: [":framework-sdkext-droidstubs-systemapi"],
-    sdk_version: "system_current",
-    visibility: [
-      "//frameworks/base:__pkg__", // Framework
-      "//frameworks/base/apex/sdkext:__pkg__", // sdkext SDK
-    ]
-}
diff --git a/apex/sdkext/Android.bp b/apex/sdkextensions/Android.bp
similarity index 83%
rename from apex/sdkext/Android.bp
rename to apex/sdkextensions/Android.bp
index f62f167..4c5c2b2 100644
--- a/apex/sdkext/Android.bp
+++ b/apex/sdkextensions/Android.bp
@@ -18,21 +18,26 @@
 
 apex {
     name: "com.android.sdkext",
-    manifest: "manifest.json",
+    defaults: [ "com.android.sdkext-defaults" ],
     binaries: [ "derive_sdk" ],
-    java_libs: [ "framework-sdkext" ],
+    prebuilts: [ "cur_sdkinfo" ],
+    manifest: "manifest.json",
+}
+
+apex_defaults {
+    name: "com.android.sdkext-defaults",
+    java_libs: [ "framework-sdkextensions" ],
     prebuilts: [
-      "com.android.sdkext.ldconfig",
-      "cur_sdkinfo",
-      "derive_sdk.rc",
+        "com.android.sdkext.ldconfig",
+        "derive_sdk.rc",
     ],
     key: "com.android.sdkext.key",
     certificate: ":com.android.sdkext.certificate",
 }
 
 sdk {
-    name: "sdkext-sdk",
-    java_header_libs: [ "framework-sdkext-stubs-systemapi" ],
+    name: "sdkextensions-sdk",
+    java_header_libs: [ "framework-sdkextensions-stubs-systemapi" ],
 }
 
 apex_key {
diff --git a/apex/sdkext/OWNERS b/apex/sdkextensions/OWNERS
similarity index 100%
rename from apex/sdkext/OWNERS
rename to apex/sdkextensions/OWNERS
diff --git a/apex/sdkextensions/TEST_MAPPING b/apex/sdkextensions/TEST_MAPPING
new file mode 100644
index 0000000..4e18833
--- /dev/null
+++ b/apex/sdkextensions/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSdkExtensionsTestCases"
+    },
+    {
+      "name": "apiextensions_e2e_tests"
+    }
+  ]
+}
diff --git a/apex/sdkext/com.android.sdkext.avbpubkey b/apex/sdkextensions/com.android.sdkext.avbpubkey
similarity index 100%
rename from apex/sdkext/com.android.sdkext.avbpubkey
rename to apex/sdkextensions/com.android.sdkext.avbpubkey
Binary files differ
diff --git a/apex/sdkext/com.android.sdkext.pem b/apex/sdkextensions/com.android.sdkext.pem
similarity index 100%
rename from apex/sdkext/com.android.sdkext.pem
rename to apex/sdkextensions/com.android.sdkext.pem
diff --git a/apex/sdkext/com.android.sdkext.pk8 b/apex/sdkextensions/com.android.sdkext.pk8
similarity index 100%
rename from apex/sdkext/com.android.sdkext.pk8
rename to apex/sdkextensions/com.android.sdkext.pk8
Binary files differ
diff --git a/apex/sdkext/com.android.sdkext.x509.pem b/apex/sdkextensions/com.android.sdkext.x509.pem
similarity index 100%
rename from apex/sdkext/com.android.sdkext.x509.pem
rename to apex/sdkextensions/com.android.sdkext.x509.pem
diff --git a/apex/sdkext/derive_sdk/Android.bp b/apex/sdkextensions/derive_sdk/Android.bp
similarity index 63%
rename from apex/sdkext/derive_sdk/Android.bp
rename to apex/sdkextensions/derive_sdk/Android.bp
index c4e3c29..cf49902 100644
--- a/apex/sdkext/derive_sdk/Android.bp
+++ b/apex/sdkextensions/derive_sdk/Android.bp
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-cc_binary {
-    name: "derive_sdk",
+cc_defaults {
+    name: "derive_sdk-defaults",
     srcs: [
         "derive_sdk.cpp",
         "sdk.proto",
@@ -30,6 +30,24 @@
     ],
 }
 
+cc_binary {
+    name: "derive_sdk",
+    defaults: [ "derive_sdk-defaults" ],
+    apex_available: [ "com.android.sdkext" ],
+    visibility: [ "//frameworks/base/apex/sdkextensions" ]
+}
+
+// Work around testing using a 64-bit test suite on 32-bit test device by
+// using a prefer32 version of derive_sdk in testing.
+cc_binary {
+    name: "derive_sdk_prefer32",
+    defaults: [ "derive_sdk-defaults" ],
+    compile_multilib: "prefer32",
+    stem: "derive_sdk",
+    apex_available: [ "test_com.android.sdkext" ],
+    visibility: [ "//frameworks/base/apex/sdkextensions/testing" ]
+}
+
 prebuilt_etc {
     name: "derive_sdk.rc",
     src: "derive_sdk.rc",
diff --git a/apex/sdkext/derive_sdk/derive_sdk.cpp b/apex/sdkextensions/derive_sdk/derive_sdk.cpp
similarity index 97%
rename from apex/sdkext/derive_sdk/derive_sdk.cpp
rename to apex/sdkextensions/derive_sdk/derive_sdk.cpp
index 0a97116..6fb7ef4 100644
--- a/apex/sdkext/derive_sdk/derive_sdk.cpp
+++ b/apex/sdkextensions/derive_sdk/derive_sdk.cpp
@@ -26,7 +26,7 @@
 #include <android-base/logging.h>
 #include <android-base/properties.h>
 
-#include "frameworks/base/apex/sdkext/derive_sdk/sdk.pb.h"
+#include "frameworks/base/apex/sdkextensions/derive_sdk/sdk.pb.h"
 
 using com::android::sdkext::proto::SdkVersion;
 
diff --git a/apex/sdkext/derive_sdk/derive_sdk.rc b/apex/sdkextensions/derive_sdk/derive_sdk.rc
similarity index 100%
rename from apex/sdkext/derive_sdk/derive_sdk.rc
rename to apex/sdkextensions/derive_sdk/derive_sdk.rc
diff --git a/apex/sdkext/derive_sdk/sdk.proto b/apex/sdkextensions/derive_sdk/sdk.proto
similarity index 100%
rename from apex/sdkext/derive_sdk/sdk.proto
rename to apex/sdkextensions/derive_sdk/sdk.proto
diff --git a/apex/sdkextensions/framework/Android.bp b/apex/sdkextensions/framework/Android.bp
new file mode 100644
index 0000000..dd17473
--- /dev/null
+++ b/apex/sdkextensions/framework/Android.bp
@@ -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 {
+    default_visibility: [ ":__pkg__" ]
+}
+
+filegroup {
+    name: "framework-sdkextensions-sources",
+    srcs: [
+        "java/**/*.java",
+    ],
+    path: "java",
+    visibility: [ "//frameworks/base" ] // For the "global" stubs.
+}
+
+java_library {
+    name: "framework-sdkextensions",
+    srcs: [ ":framework-sdkextensions-sources" ],
+    sdk_version: "system_current",
+    libs: [ "framework-annotations-lib" ],
+    permitted_packages: [ "android.os.ext" ],
+    installable: true,
+    visibility: [
+        "//frameworks/base/apex/sdkextensions",
+        "//frameworks/base/apex/sdkextensions/testing",
+    ],
+    hostdex: true, // for hiddenapi check
+    apex_available: [
+        "com.android.sdkext",
+        "test_com.android.sdkext",
+    ],
+}
+
+droidstubs {
+    name: "framework-sdkextensions-droidstubs-publicapi",
+    defaults: [
+        "framework-sdkextensions-stubs-defaults",
+        "framework-module-stubs-defaults-publicapi",
+    ]
+}
+
+droidstubs {
+    name: "framework-sdkextensions-droidstubs-systemapi",
+    defaults: [
+        "framework-sdkextensions-stubs-defaults",
+        "framework-module-stubs-defaults-systemapi",
+    ]
+}
+
+stubs_defaults {
+    name: "framework-sdkextensions-stubs-defaults",
+    srcs: [
+        ":framework-sdkextensions-sources",
+        ":framework-annotations",
+    ],
+    sdk_version: "system_current",
+}
+
+java_library {
+    name: "framework-sdkextensions-stubs-systemapi",
+    srcs: [":framework-sdkextensions-droidstubs-systemapi"],
+    sdk_version: "system_current",
+    visibility: [
+        "//frameworks/base", // Framework
+        "//frameworks/base/apex/sdkextensions", // sdkextensions SDK
+    ]
+}
diff --git a/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java b/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java
similarity index 100%
rename from apex/sdkext/framework/java/android/os/ext/SdkExtensions.java
rename to apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java
diff --git a/apex/sdkext/framework/java/android/os/ext/package.html b/apex/sdkextensions/framework/java/android/os/ext/package.html
similarity index 100%
rename from apex/sdkext/framework/java/android/os/ext/package.html
rename to apex/sdkextensions/framework/java/android/os/ext/package.html
diff --git a/apex/sdkext/gen_sdkinfo.py b/apex/sdkextensions/gen_sdkinfo.py
similarity index 100%
rename from apex/sdkext/gen_sdkinfo.py
rename to apex/sdkextensions/gen_sdkinfo.py
diff --git a/apex/sdkext/ld.config.txt b/apex/sdkextensions/ld.config.txt
similarity index 91%
rename from apex/sdkext/ld.config.txt
rename to apex/sdkextensions/ld.config.txt
index b447068..dcc69b8 100644
--- a/apex/sdkext/ld.config.txt
+++ b/apex/sdkextensions/ld.config.txt
@@ -1,10 +1,10 @@
 # Copyright (C) 2019 The Android Open Source Project
 #
-# Bionic loader config file for the sdkext apex.
+# Bionic loader config file for the sdkextensions apex.
 
-dir.sdkext = /apex/com.android.sdkext/bin/
+dir.sdkextensions = /apex/com.android.sdkext/bin/
 
-[sdkext]
+[sdkextensions]
 additional.namespaces = platform
 
 namespace.default.isolated = true
diff --git a/apex/sdkext/manifest.json b/apex/sdkextensions/manifest.json
similarity index 100%
rename from apex/sdkext/manifest.json
rename to apex/sdkextensions/manifest.json
diff --git a/apex/sdkext/sdk.proto b/apex/sdkextensions/sdk.proto
similarity index 100%
rename from apex/sdkext/sdk.proto
rename to apex/sdkextensions/sdk.proto
diff --git a/apex/sdkextensions/testing/Android.bp b/apex/sdkextensions/testing/Android.bp
new file mode 100644
index 0000000..e6451cc
--- /dev/null
+++ b/apex/sdkextensions/testing/Android.bp
@@ -0,0 +1,46 @@
+// 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.
+
+apex {
+    name: "test_com.android.sdkext",
+    visibility: [ "//system/apex/tests" ],
+    defaults: ["com.android.sdkext-defaults"],
+    manifest: "test_manifest.json",
+    prebuilts: [ "sdkinfo_45" ],
+    file_contexts: ":com.android.sdkext-file_contexts",
+    installable: false, // Should never be installed on the systemimage
+    multilib: {
+        prefer32: {
+            binaries: ["derive_sdk_prefer32"],
+        },
+    },
+    // The automated test infra ends up building this apex for 64+32-bit and
+    // then installs it on a 32-bit-only device. Work around this weirdness
+    // by preferring 32-bit.
+    compile_multilib: "prefer32",
+}
+
+genrule {
+    name: "sdkinfo_45_src",
+    out: [ "sdkinfo.binarypb" ],
+    tools: [ "gen_sdkinfo" ],
+    cmd: "$(location) -v 45 -o $(out)",
+}
+
+prebuilt_etc {
+    name: "sdkinfo_45",
+    src: ":sdkinfo_45_src",
+    filename: "sdkinfo.binarypb",
+    installable: false,
+}
diff --git a/apex/sdkextensions/testing/test_manifest.json b/apex/sdkextensions/testing/test_manifest.json
new file mode 100644
index 0000000..1b4a2b0
--- /dev/null
+++ b/apex/sdkextensions/testing/test_manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.sdkext",
+  "version": 2147483647
+}
diff --git a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
index 21b7767..99b9d39 100644
--- a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
+++ b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
@@ -67,11 +67,4 @@
 
     /** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */
     oneway void triggerUidSnapshot();
-
-    /** Tells StatsCompanionService to tell statsd to register a puller for the given atom id */
-    oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
-            in int[] additiveFields, IPullAtomCallback pullerCallback);
-
-    /** Tells StatsCompanionService to tell statsd to unregister a puller for the given atom id */
-    oneway void unregisterPullAtomCallback(int atomTag);
 }
diff --git a/apex/statsd/aidl/android/os/IStatsManagerService.aidl b/apex/statsd/aidl/android/os/IStatsManagerService.aidl
index 05583677..4a259f5 100644
--- a/apex/statsd/aidl/android/os/IStatsManagerService.aidl
+++ b/apex/statsd/aidl/android/os/IStatsManagerService.aidl
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.app.PendingIntent;
+import android.os.IPullAtomCallback;
 
 /**
   * Binder interface to communicate with the Java-based statistics service helper.
@@ -85,4 +86,51 @@
      * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
      */
     void unsetBroadcastSubscriber(long configKey, long subscriberId, in String packageName);
-}
\ No newline at end of file
+
+    /**
+     * Returns the most recently registered experiment IDs.
+     *
+     * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+     */
+    long[] getRegisteredExperimentIds();
+
+    /**
+     * Fetches metadata across statsd. Returns byte array representing wire-encoded proto.
+     *
+     * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+     */
+    byte[] getMetadata(in String packageName);
+
+    /**
+     * Fetches data for the specified configuration key. Returns a byte array representing proto
+     * wire-encoded of ConfigMetricsReportList.
+     *
+     * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+     */
+    byte[] getData(in long key, in String packageName);
+
+    /**
+     * Sets a configuration with the specified config id and subscribes to updates for this
+     * configuration id. Broadcasts will be sent if this configuration needs to be collected.
+     * The configuration must be a wire-encoded StatsdConfig. The receiver for this data is
+     * registered in a separate function.
+     *
+     * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+     */
+    void addConfiguration(in long configId, in byte[] config, in String packageName);
+
+    /**
+     * Removes the configuration with the matching config id. No-op if this config id does not
+     * exist.
+     *
+     * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+     */
+    void removeConfiguration(in long configId, in String packageName);
+
+    /** Tell StatsManagerService to register a puller for the given atom tag with statsd. */
+    oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
+            in int[] additiveFields, IPullAtomCallback pullerCallback);
+
+    /** Tell StatsManagerService to unregister the pulller for the given atom tag from statsd. */
+    oneway void unregisterPullAtomCallback(int atomTag);
+}
diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl
index 9358439..c409f51 100644
--- a/apex/statsd/aidl/android/os/IStatsd.aidl
+++ b/apex/statsd/aidl/android/os/IStatsd.aidl
@@ -89,24 +89,24 @@
      *
      * Requires Manifest.permission.DUMP.
      */
-    byte[] getData(in long key, in String packageName);
+    byte[] getData(in long key, int callingUid);
 
     /**
      * Fetches metadata across statsd. Returns byte array representing wire-encoded proto.
      *
      * Requires Manifest.permission.DUMP.
      */
-    byte[] getMetadata(in String packageName);
+    byte[] getMetadata();
 
     /**
-     * Sets a configuration with the specified config key and subscribes to updates for this
+     * Sets a configuration with the specified config id and subscribes to updates for this
      * configuration key. Broadcasts will be sent if this configuration needs to be collected.
      * The configuration must be a wire-encoded StatsdConfig. The receiver for this data is
      * registered in a separate function.
      *
      * Requires Manifest.permission.DUMP.
      */
-    void addConfiguration(in long configKey, in byte[] config, in String packageName);
+    void addConfiguration(in long configId, in byte[] config, in int callingUid);
 
     /**
      * Registers the given pending intent for this config key. This intent is invoked when the
@@ -143,12 +143,12 @@
     void removeActiveConfigsChangedOperation(int callingUid);
 
     /**
-     * Removes the configuration with the matching config key. No-op if this config key does not
+     * Removes the configuration with the matching config id. No-op if this config id does not
      * exist.
      *
      * Requires Manifest.permission.DUMP.
      */
-    void removeConfiguration(in long configKey, in String packageName);
+    void removeConfiguration(in long configId, in int callingUid);
 
     /**
      * Set the PendingIntentRef to be used when broadcasting subscriber
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
index a2b0577..0b46645a 100644
--- a/apex/statsd/framework/Android.bp
+++ b/apex/statsd/framework/Android.bp
@@ -37,7 +37,16 @@
         // TODO(b/146230220): Use framework-system-stubs instead.
         "android_system_stubs_current",
     ],
-    // TODO:(b/146210774): Add apex_available field.
+    hostdex: true, // for hiddenapi check
+    visibility: [
+        "//frameworks/base/apex/statsd:__subpackages__",
+        //TODO(b/146167933) remove this when framework is built with framework-statsd-stubs
+        "//frameworks/base",
+    ],
+    apex_available: [
+        "com.android.os.statsd",
+        "test_com.android.os.statsd",
+    ],
 }
 
 droidstubs {
diff --git a/apex/statsd/service/Android.bp b/apex/statsd/service/Android.bp
index f3a8989..9103848 100644
--- a/apex/statsd/service/Android.bp
+++ b/apex/statsd/service/Android.bp
@@ -13,4 +13,8 @@
         "framework-minus-apex",
         "services.core",
     ],
+    apex_available: [
+        "com.android.os.statsd",
+        "test_com.android.os.statsd",
+    ],
 }
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index d57afee..0f981e2 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -75,7 +75,6 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.IPullAtomCallback;
 import android.os.IStatsCompanionService;
 import android.os.IStatsd;
 import android.os.IStoraged;
@@ -263,71 +262,6 @@
 
     private StatsManagerService mStatsManagerService;
 
-    private static final class PullerKey {
-        private final int mUid;
-        private final int mAtomTag;
-
-        PullerKey(int uid, int atom) {
-            mUid = uid;
-            mAtomTag = atom;
-        }
-
-        public int getUid() {
-            return mUid;
-        }
-
-        public int getAtom() {
-            return mAtomTag;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mUid, mAtomTag);
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj instanceof PullerKey) {
-                PullerKey other = (PullerKey) obj;
-                return this.mUid == other.getUid() && this.mAtomTag == other.getAtom();
-            }
-            return false;
-        }
-    }
-
-    private static final class PullerValue {
-        private final long mCoolDownNs;
-        private final long mTimeoutNs;
-        private int[] mAdditiveFields;
-        private IPullAtomCallback mCallback;
-
-        PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields,
-                IPullAtomCallback callback) {
-            mCoolDownNs = coolDownNs;
-            mTimeoutNs = timeoutNs;
-            mAdditiveFields = additiveFields;
-            mCallback = callback;
-        }
-
-        public long getCoolDownNs() {
-            return mCoolDownNs;
-        }
-
-        public long getTimeoutNs() {
-            return mTimeoutNs;
-        }
-
-        public int[] getAdditiveFields() {
-            return mAdditiveFields;
-        }
-
-        public IPullAtomCallback getCallback() {
-            return mCallback;
-        }
-    }
-
-    private final HashMap<PullerKey, PullerValue> mPullers = new HashMap<>();
-
     private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
     private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
     private WifiManager mWifiManager = null;
@@ -881,31 +815,6 @@
         }
     }
 
-    private void pullWifiBytesTransfer(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        long token = Binder.clearCallingIdentity();
-        try {
-            // TODO: Consider caching the following call to get BatteryStatsInternal.
-            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
-            String[] ifaces = bs.getWifiIfaces();
-            if (ifaces.length == 0) {
-                return;
-            }
-            if (mNetworkStatsService == null) {
-                Slog.e(TAG, "NetworkStats Service is not available!");
-                return;
-            }
-            // Combine all the metrics per Uid into one record.
-            NetworkStats stats = mNetworkStatsService.getDetailedUidStats(ifaces).groupedByUid();
-            addNetworkStats(tagId, pulledData, stats, false);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Pulling netstats for wifi bytes has error", e);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
     private void pullWifiBytesTransferByFgBg(
             int tagId, long elapsedNanos, long wallClockNanos,
             List<StatsLogEventWrapper> pulledData) {
@@ -2382,149 +2291,180 @@
         long elapsedNanos = SystemClock.elapsedRealtimeNanos();
         long wallClockNanos = SystemClock.currentTimeMicro() * 1000L;
         switch (tagId) {
-            case StatsLog.WIFI_BYTES_TRANSFER: {
-                pullWifiBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
+            
             case StatsLog.MOBILE_BYTES_TRANSFER: {
                 pullMobileBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: {
                 pullWifiBytesTransferByFgBg(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: {
                 pullMobileBytesTransferByFgBg(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.BLUETOOTH_BYTES_TRANSFER: {
                 pullBluetoothBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.KERNEL_WAKELOCK: {
                 pullKernelWakelock(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.CPU_TIME_PER_FREQ: {
                 pullCpuTimePerFreq(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.CPU_TIME_PER_UID: {
                 pullKernelUidCpuTime(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.CPU_TIME_PER_UID_FREQ: {
                 pullKernelUidCpuFreqTime(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.CPU_CLUSTER_TIME: {
                 pullKernelUidCpuClusterTime(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.CPU_ACTIVE_TIME: {
                 pullKernelUidCpuActiveTime(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.WIFI_ACTIVITY_INFO: {
                 pullWifiActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.MODEM_ACTIVITY_INFO: {
                 pullModemActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.BLUETOOTH_ACTIVITY_INFO: {
                 pullBluetoothActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.SYSTEM_UPTIME: {
                 pullSystemUpTime(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.SYSTEM_ELAPSED_REALTIME: {
                 pullSystemElapsedRealtime(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.PROCESS_MEMORY_STATE: {
                 pullProcessMemoryState(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.PROCESS_MEMORY_HIGH_WATER_MARK: {
                 pullProcessMemoryHighWaterMark(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.PROCESS_MEMORY_SNAPSHOT: {
                 pullProcessMemorySnapshot(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.SYSTEM_ION_HEAP_SIZE: {
                 pullSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.PROCESS_SYSTEM_ION_HEAP_SIZE: {
                 pullProcessSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.BINDER_CALLS: {
                 pullBinderCallsStats(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.BINDER_CALLS_EXCEPTIONS: {
                 pullBinderCallsStatsExceptions(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.LOOPER_STATS: {
                 pullLooperStats(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DISK_STATS: {
                 pullDiskStats(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DIRECTORY_USAGE: {
                 pullDirectoryUsage(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.APP_SIZE: {
                 pullAppSize(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.CATEGORY_SIZE: {
                 pullCategorySize(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.NUM_FINGERPRINTS_ENROLLED: {
                 pullNumBiometricsEnrolled(BiometricsProtoEnums.MODALITY_FINGERPRINT, tagId,
                         elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.NUM_FACES_ENROLLED: {
                 pullNumBiometricsEnrolled(BiometricsProtoEnums.MODALITY_FACE, tagId, elapsedNanos,
                         wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.PROC_STATS: {
                 pullProcessStats(ProcessStats.REPORT_ALL, tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.PROC_STATS_PKG_PROC: {
                 pullProcessStats(ProcessStats.REPORT_PKG_PROC_STATS, tagId, elapsedNanos,
                         wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DISK_IO: {
                 pullDiskIo(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.POWER_PROFILE: {
                 pullPowerProfile(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.BUILD_INFORMATION: {
                 pullBuildInformation(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.PROCESS_CPU_TIME: {
                 pullProcessCpuTime(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
@@ -2533,73 +2473,90 @@
                 pullCpuTimePerThreadFreq(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DEVICE_CALCULATED_POWER_USE: {
                 pullDeviceCalculatedPowerUse(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DEVICE_CALCULATED_POWER_BLAME_UID: {
                 pullDeviceCalculatedPowerBlameUid(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER: {
                 pullDeviceCalculatedPowerBlameOther(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.TEMPERATURE: {
                 pullTemperature(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.COOLING_DEVICE: {
                 pullCoolingDevices(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DEBUG_ELAPSED_CLOCK: {
                 pullDebugElapsedClock(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DEBUG_FAILING_ELAPSED_CLOCK: {
                 pullDebugFailingElapsedClock(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.ROLE_HOLDER: {
                 pullRoleHolders(elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DANGEROUS_PERMISSION_STATE: {
                 pullDangerousPermissionState(StatsLog.DANGEROUS_PERMISSION_STATE, elapsedNanos,
                         wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED: {
                 pullDangerousPermissionState(StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED,
                         elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.TIME_ZONE_DATA_INFO: {
                 pullTimeZoneDataInfo(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.EXTERNAL_STORAGE_INFO: {
                 pullExternalStorageInfo(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.APPS_ON_EXTERNAL_STORAGE_INFO: {
                 pullAppsOnExternalStorageInfo(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.FACE_SETTINGS: {
                 pullFaceSettings(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.APP_OPS: {
                 pullAppOps(elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.NOTIFICATION_REMOTE_VIEWS: {
                 pullNotificationStats(NotificationManagerService.REPORT_REMOTE_VIEWS,
                         tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             default:
                 Slog.w(TAG, "No such tagId data as " + tagId);
                 return null;
@@ -2634,57 +2591,6 @@
         }
     }
 
-    @Override
-    public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
-            int[] additiveFields, IPullAtomCallback pullerCallback) {
-        synchronized (sStatsdLock) {
-            // Always cache the puller in SCS.
-            // If statsd is down, we will register it when it comes back up.
-            int callingUid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            PullerKey key = new PullerKey(callingUid, atomTag);
-            PullerValue val = new PullerValue(
-                    coolDownNs, timeoutNs, additiveFields, pullerCallback);
-            mPullers.put(key, val);
-
-            if (sStatsd == null) {
-                Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag);
-                return;
-            }
-            try {
-                sStatsd.registerPullAtomCallback(
-                        callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-    }
-
-    @Override
-    public void unregisterPullAtomCallback(int atomTag) {
-        synchronized (sStatsdLock) {
-            // Always remove the puller in SCS.
-            // If statsd is down, we will not register it when it comes back up.
-            int callingUid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            PullerKey key = new PullerKey(callingUid, atomTag);
-            mPullers.remove(key);
-
-            if (sStatsd == null) {
-                Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag);
-                return;
-            }
-            try {
-                sStatsd.unregisterPullAtomCallback(callingUid, atomTag);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-    }
 
     // Statsd related code
 
@@ -2763,8 +2669,6 @@
                     // Pull the latest state of UID->app name, version mapping when
                     // statsd starts.
                     informAllUidsLocked(mContext);
-                    // Register all pullers. If SCS has just started, this should be empty.
-                    registerAllPullersLocked();
                 } finally {
                     restoreCallingIdentity(token);
                 }
@@ -2776,17 +2680,6 @@
         }
     }
 
-    @GuardedBy("sStatsdLock")
-    private void registerAllPullersLocked() throws RemoteException {
-        // TODO: pass in one call, using a file descriptor (similar to uidmap).
-        for (Map.Entry<PullerKey, PullerValue> entry : mPullers.entrySet()) {
-            PullerKey key = entry.getKey();
-            PullerValue val = entry.getValue();
-            sStatsd.registerPullAtomCallback(key.getUid(), key.getAtom(), val.getCoolDownNs(),
-                    val.getTimeoutNs(), val.getAdditiveFields(), val.getCallback());
-        }
-    }
-
     private class StatsdDeathRecipient implements IBinder.DeathRecipient {
         @Override
         public void binderDied() {
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
index 1d03e3b..04d8b00 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
@@ -19,10 +19,12 @@
 import static com.android.server.stats.StatsCompanion.PendingIntentRef;
 
 import android.Manifest;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.os.Binder;
+import android.os.IPullAtomCallback;
 import android.os.IStatsManagerService;
 import android.os.IStatsd;
 import android.os.Process;
@@ -59,8 +61,7 @@
     @GuardedBy("mLock")
     private ArrayMap<ConfigKey, PendingIntentRef> mDataFetchPirMap = new ArrayMap<>();
     @GuardedBy("mLock")
-    private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap =
-            new ArrayMap<>();
+    private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap = new ArrayMap<>();
     @GuardedBy("mLock")
     private ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> mBroadcastSubscriberPirMap =
             new ArrayMap<>();
@@ -71,8 +72,8 @@
     }
 
     private static class ConfigKey {
-        private int mUid;
-        private long mConfigId;
+        private final int mUid;
+        private final long mConfigId;
 
         ConfigKey(int uid, long configId) {
             mUid = uid;
@@ -102,6 +103,126 @@
         }
     }
 
+    private static class PullerKey {
+        private final int mUid;
+        private final int mAtomTag;
+
+        PullerKey(int uid, int atom) {
+            mUid = uid;
+            mAtomTag = atom;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public int getAtom() {
+            return mAtomTag;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mUid, mAtomTag);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof PullerKey) {
+                PullerKey other = (PullerKey) obj;
+                return this.mUid == other.getUid() && this.mAtomTag == other.getAtom();
+            }
+            return false;
+        }
+    }
+
+    private static class PullerValue {
+        private final long mCoolDownNs;
+        private final long mTimeoutNs;
+        private final int[] mAdditiveFields;
+        private final IPullAtomCallback mCallback;
+
+        PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields,
+                IPullAtomCallback callback) {
+            mCoolDownNs = coolDownNs;
+            mTimeoutNs = timeoutNs;
+            mAdditiveFields = additiveFields;
+            mCallback = callback;
+        }
+
+        public long getCoolDownNs() {
+            return mCoolDownNs;
+        }
+
+        public long getTimeoutNs() {
+            return mTimeoutNs;
+        }
+
+        public int[] getAdditiveFields() {
+            return mAdditiveFields;
+        }
+
+        public IPullAtomCallback getCallback() {
+            return mCallback;
+        }
+    }
+
+    private final ArrayMap<PullerKey, PullerValue> mPullers = new ArrayMap<>();
+
+    @Override
+    public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
+            int[] additiveFields, IPullAtomCallback pullerCallback) {
+        int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        PullerKey key = new PullerKey(callingUid, atomTag);
+        PullerValue val = new PullerValue(coolDownNs, timeoutNs, additiveFields, pullerCallback);
+
+        // Always cache the puller in StatsManagerService. If statsd is down, we will register the
+        // puller when statsd comes back up.
+        synchronized (mLock) {
+            mPullers.put(key, val);
+        }
+
+        IStatsd statsd = getStatsdNonblocking();
+        if (statsd == null) {
+            return;
+        }
+
+        try {
+            statsd.registerPullAtomCallback(
+                    callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void unregisterPullAtomCallback(int atomTag) {
+        int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        PullerKey key = new PullerKey(callingUid, atomTag);
+
+        // Always remove the puller from StatsManagerService even if statsd is down. When statsd
+        // comes back up, we will not re-register the removed puller.
+        synchronized (mLock) {
+            mPullers.remove(key);
+        }
+
+        IStatsd statsd = getStatsdNonblocking();
+        if (statsd == null) {
+            return;
+        }
+
+        try {
+            statsd.unregisterPullAtomCallback(callingUid, atomTag);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     @Override
     public void setDataFetchOperation(long configId, PendingIntent pendingIntent,
             String packageName) {
@@ -245,20 +366,127 @@
         }
     }
 
+    @Override
+    public long[] getRegisteredExperimentIds() throws IllegalStateException {
+        enforceDumpAndUsageStatsPermission(null);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            IStatsd statsd = waitForStatsd();
+            if (statsd != null) {
+                return statsd.getRegisteredExperimentIds();
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to getRegisteredExperimentIds with statsd");
+            throw new IllegalStateException(e.getMessage(), e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        throw new IllegalStateException("Failed to connect to statsd to registerExperimentIds");
+    }
+
+    @Override
+    public byte[] getMetadata(String packageName) throws IllegalStateException {
+        enforceDumpAndUsageStatsPermission(packageName);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            IStatsd statsd = waitForStatsd();
+            if (statsd != null) {
+                return statsd.getMetadata();
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to getMetadata with statsd");
+            throw new IllegalStateException(e.getMessage(), e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        throw new IllegalStateException("Failed to connect to statsd to getMetadata");
+    }
+
+    @Override
+    public byte[] getData(long key, String packageName) throws IllegalStateException {
+        enforceDumpAndUsageStatsPermission(packageName);
+        int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            IStatsd statsd = waitForStatsd();
+            if (statsd != null) {
+                return statsd.getData(key, callingUid);
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to getData with statsd");
+            throw new IllegalStateException(e.getMessage(), e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        throw new IllegalStateException("Failed to connect to statsd to getData");
+    }
+
+    @Override
+    public void addConfiguration(long configId, byte[] config, String packageName)
+            throws IllegalStateException {
+        enforceDumpAndUsageStatsPermission(packageName);
+        int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            IStatsd statsd = waitForStatsd();
+            if (statsd != null) {
+                statsd.addConfiguration(configId, config, callingUid);
+                return;
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to addConfiguration with statsd");
+            throw new IllegalStateException(e.getMessage(), e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        throw new IllegalStateException("Failed to connect to statsd to addConfig");
+    }
+
+    @Override
+    public void removeConfiguration(long configId, String packageName)
+            throws IllegalStateException {
+        enforceDumpAndUsageStatsPermission(packageName);
+        int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            IStatsd statsd = waitForStatsd();
+            if (statsd != null) {
+                statsd.removeConfiguration(configId, callingUid);
+                return;
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to removeConfiguration with statsd");
+            throw new IllegalStateException(e.getMessage(), e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        throw new IllegalStateException("Failed to connect to statsd to removeConfig");
+    }
+
     void setStatsCompanionService(StatsCompanionService statsCompanionService) {
         mStatsCompanionService = statsCompanionService;
     }
 
-    private void enforceDumpAndUsageStatsPermission(String packageName) {
+    /**
+     * Checks that the caller has both DUMP and PACKAGE_USAGE_STATS permissions. Also checks that
+     * the caller has USAGE_STATS_PERMISSION_OPS for the specified packageName if it is not null.
+     *
+     * @param packageName The packageName to check USAGE_STATS_PERMISSION_OPS.
+     */
+    private void enforceDumpAndUsageStatsPermission(@Nullable String packageName) {
         int callingUid = Binder.getCallingUid();
         int callingPid = Binder.getCallingPid();
 
         if (callingPid == Process.myPid()) {
             return;
         }
+
         mContext.enforceCallingPermission(Manifest.permission.DUMP, null);
         mContext.enforceCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS, null);
 
+        if (packageName == null) {
+            return;
+        }
         AppOpsManager appOpsManager = (AppOpsManager) mContext
                 .getSystemService(Context.APP_OPS_SERVICE);
         switch (appOpsManager.noteOp(USAGE_STATS_PERMISSION_OPS,
@@ -333,46 +561,85 @@
         if (statsd == null) {
             return;
         }
-        // Since we do not want to make an IPC with the a lock held, we first create local deep
-        // copies of the data with the lock held before iterating through the maps.
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            registerAllPullers(statsd);
+            registerAllDataFetchOperations(statsd);
+            registerAllActiveConfigsChangedOperations(statsd);
+            registerAllBroadcastSubscribers(statsd);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "StatsManager failed to (re-)register data with statsd");
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Pre-condition: the Binder calling identity has already been cleared
+    private void registerAllPullers(IStatsd statsd) throws RemoteException {
+        // Since we do not want to make an IPC with the lock held, we first create a copy of the
+        // data with the lock held before iterating through the map.
+        ArrayMap<PullerKey, PullerValue> pullersCopy;
+        synchronized (mLock) {
+            pullersCopy = new ArrayMap<>(mPullers);
+        }
+
+        for (Map.Entry<PullerKey, PullerValue> entry : pullersCopy.entrySet()) {
+            PullerKey key = entry.getKey();
+            PullerValue value = entry.getValue();
+            statsd.registerPullAtomCallback(key.getUid(), key.getAtom(), value.getCoolDownNs(),
+                    value.getTimeoutNs(), value.getAdditiveFields(), value.getCallback());
+        }
+    }
+
+    // Pre-condition: the Binder calling identity has already been cleared
+    private void registerAllDataFetchOperations(IStatsd statsd) throws RemoteException {
+        // Since we do not want to make an IPC with the lock held, we first create a copy of the
+        // data with the lock held before iterating through the map.
         ArrayMap<ConfigKey, PendingIntentRef> dataFetchCopy;
-        ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy;
-        ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy;
         synchronized (mLock) {
             dataFetchCopy = new ArrayMap<>(mDataFetchPirMap);
-            activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap);
-            broadcastSubscriberCopy = new ArrayMap<>();
-            for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry
-                    : mBroadcastSubscriberPirMap.entrySet()) {
-                broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap<>(entry.getValue()));
-            }
         }
+
         for (Map.Entry<ConfigKey, PendingIntentRef> entry : dataFetchCopy.entrySet()) {
             ConfigKey key = entry.getKey();
-            try {
-                statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid());
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to setDataFetchOperation from pirMap");
+            statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid());
+        }
+    }
+
+    // Pre-condition: the Binder calling identity has already been cleared
+    private void registerAllActiveConfigsChangedOperations(IStatsd statsd) throws RemoteException {
+        // Since we do not want to make an IPC with the lock held, we first create a copy of the
+        // data with the lock held before iterating through the map.
+        ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy;
+        synchronized (mLock) {
+            activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap);
+        }
+
+        for (Map.Entry<Integer, PendingIntentRef> entry : activeConfigsChangedCopy.entrySet()) {
+            statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey());
+        }
+    }
+
+    // Pre-condition: the Binder calling identity has already been cleared
+    private void registerAllBroadcastSubscribers(IStatsd statsd) throws RemoteException {
+        // Since we do not want to make an IPC with the lock held, we first create a deep copy of
+        // the data with the lock held before iterating through the map.
+        ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy =
+                new ArrayMap<>();
+        synchronized (mLock) {
+            for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry :
+                    mBroadcastSubscriberPirMap.entrySet()) {
+                broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap(entry.getValue()));
             }
         }
-        for (Map.Entry<Integer, PendingIntentRef> entry
-                : activeConfigsChangedCopy.entrySet()) {
-            try {
-                statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey());
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to setActiveConfigsChangedOperation from pirMap");
-            }
-        }
-        for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry
-                : broadcastSubscriberCopy.entrySet()) {
+
+        for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry :
+                mBroadcastSubscriberPirMap.entrySet()) {
+            ConfigKey configKey = entry.getKey();
             for (Map.Entry<Long, PendingIntentRef> subscriberEntry : entry.getValue().entrySet()) {
-                ConfigKey configKey = entry.getKey();
-                try {
-                    statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(),
-                            subscriberEntry.getValue(), configKey.getUid());
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Failed to setBroadcastSubscriber from pirMap");
-                }
+                statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(),
+                        subscriberEntry.getValue(), configKey.getUid());
             }
         }
     }
diff --git a/api/current.txt b/api/current.txt
index 05fed23..1d50359 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -115,6 +115,7 @@
     field public static final String READ_LOGS = "android.permission.READ_LOGS";
     field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
     field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+    field public static final String READ_PRECISE_PHONE_STATE = "android.permission.READ_PRECISE_PHONE_STATE";
     field public static final String READ_SMS = "android.permission.READ_SMS";
     field public static final String READ_SYNC_SETTINGS = "android.permission.READ_SYNC_SETTINGS";
     field public static final String READ_SYNC_STATS = "android.permission.READ_SYNC_STATS";
@@ -1144,6 +1145,7 @@
     field public static final int resizeable = 16843405; // 0x101028d
     field public static final int resizeableActivity = 16844022; // 0x10104f6
     field public static final int resource = 16842789; // 0x1010025
+    field public static final int resourcesMap = 16844297; // 0x1010609
     field public static final int restoreAnyVersion = 16843450; // 0x10102ba
     field @Deprecated public static final int restoreNeedsApplication = 16843421; // 0x101029d
     field public static final int restrictedAccountType = 16843733; // 0x10103d5
@@ -2923,6 +2925,7 @@
     method public int getShowMode();
     method public boolean removeOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
     method public boolean setShowMode(int);
+    method public boolean switchToInputMethod(@NonNull String);
   }
 
   public static interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener {
@@ -5837,6 +5840,7 @@
     method public void enableLights(boolean);
     method public void enableVibration(boolean);
     method public android.media.AudioAttributes getAudioAttributes();
+    method @Nullable public String getConversationId();
     method public String getDescription();
     method public String getGroup();
     method public String getId();
@@ -5844,12 +5848,14 @@
     method public int getLightColor();
     method public int getLockscreenVisibility();
     method public CharSequence getName();
+    method @Nullable public String getParentChannelId();
     method public android.net.Uri getSound();
     method public long[] getVibrationPattern();
     method public boolean hasUserSetImportance();
     method public boolean hasUserSetSound();
     method public void setAllowBubbles(boolean);
     method public void setBypassDnd(boolean);
+    method public void setConversationId(@Nullable String, @Nullable String);
     method public void setDescription(String);
     method public void setGroup(String);
     method public void setImportance(int);
@@ -5862,6 +5868,7 @@
     method public boolean shouldShowLights();
     method public boolean shouldVibrate();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s";
     field @NonNull public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
     field public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
   }
@@ -5903,6 +5910,7 @@
     method public final int getCurrentInterruptionFilter();
     method public int getImportance();
     method public android.app.NotificationChannel getNotificationChannel(String);
+    method @Nullable public android.app.NotificationChannel getNotificationChannel(@NonNull String, @NonNull String);
     method public android.app.NotificationChannelGroup getNotificationChannelGroup(String);
     method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
     method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
@@ -9521,6 +9529,7 @@
     method public void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]);
     method @Nullable public final String getCallingFeatureId();
     method @Nullable public final String getCallingPackage();
+    method @Nullable public final String getCallingPackageUnchecked();
     method @Nullable public final android.content.Context getContext();
     method @Nullable public final android.content.pm.PathPermission[] getPathPermissions();
     method @Nullable public final String getReadPermission();
@@ -9530,6 +9539,7 @@
     method @Nullable public abstract android.net.Uri insert(@NonNull android.net.Uri, @Nullable android.content.ContentValues);
     method @Nullable public android.net.Uri insert(@NonNull android.net.Uri, @Nullable android.content.ContentValues, @Nullable android.os.Bundle);
     method protected boolean isTemporary();
+    method public void onCallingPackageChanged();
     method public void onConfigurationChanged(android.content.res.Configuration);
     method public abstract boolean onCreate();
     method public void onLowMemory();
@@ -9644,13 +9654,13 @@
     ctor public ContentProviderResult(@NonNull android.net.Uri);
     ctor public ContentProviderResult(int);
     ctor public ContentProviderResult(@NonNull android.os.Bundle);
-    ctor public ContentProviderResult(@NonNull Exception);
+    ctor public ContentProviderResult(@NonNull Throwable);
     ctor public ContentProviderResult(android.os.Parcel);
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.ContentProviderResult> CREATOR;
     field @Nullable public final Integer count;
-    field @Nullable public final Exception exception;
+    field @Nullable public final Throwable exception;
     field @Nullable public final android.os.Bundle extras;
     field @Nullable public final android.net.Uri uri;
   }
@@ -9838,6 +9848,7 @@
     method public boolean bindIsolatedService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection);
     method public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int);
     method public boolean bindService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection);
+    method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.INTERACT_ACROSS_PROFILES}) public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle);
     method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String);
     method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int);
     method @CheckResult(suggest="#enforceCallingPermission(String,String)") public abstract int checkCallingPermission(@NonNull String);
@@ -10015,6 +10026,7 @@
     field public static final String MEDIA_ROUTER_SERVICE = "media_router";
     field public static final String MEDIA_SESSION_SERVICE = "media_session";
     field public static final String MIDI_SERVICE = "midi";
+    field public static final String MMS_SERVICE = "mms";
     field public static final int MODE_APPEND = 32768; // 0x8000
     field public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 8; // 0x8
     field @Deprecated public static final int MODE_MULTI_PROCESS = 4; // 0x4
@@ -11370,6 +11382,9 @@
   }
 
   public class CrossProfileApps {
+    method public boolean canInteractAcrossProfiles();
+    method public boolean canRequestInteractAcrossProfiles();
+    method @Nullable public android.content.Intent createRequestInteractAcrossProfilesIntent();
     method @NonNull public android.graphics.drawable.Drawable getProfileSwitchingIconDrawable(@NonNull android.os.UserHandle);
     method @NonNull public CharSequence getProfileSwitchingLabel(@NonNull android.os.UserHandle);
     method @NonNull public java.util.List<android.os.UserHandle> getTargetUserProfiles();
@@ -11942,6 +11957,7 @@
     field public static final String FEATURE_STRONGBOX_KEYSTORE = "android.hardware.strongbox_keystore";
     field public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
     field public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
+    field public static final String FEATURE_TELEPHONY_DATA = "android.hardware.telephony.data";
     field public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc";
     field public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
     field public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
@@ -13340,6 +13356,7 @@
     method @Deprecated public String buildUnionSubQuery(String, String[], java.util.Set<java.lang.String>, int, String, String, String[], String, String);
     method public int delete(@NonNull android.database.sqlite.SQLiteDatabase, @Nullable String, @Nullable String[]);
     method @Nullable public android.database.sqlite.SQLiteDatabase.CursorFactory getCursorFactory();
+    method @Nullable public java.util.Collection<java.util.regex.Pattern> getProjectionGreylist();
     method @Nullable public java.util.Map<java.lang.String,java.lang.String> getProjectionMap();
     method @Nullable public String getTables();
     method public long insert(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues);
@@ -13352,6 +13369,7 @@
     method public android.database.Cursor query(android.database.sqlite.SQLiteDatabase, String[], String, String[], String, String, String, String, android.os.CancellationSignal);
     method public void setCursorFactory(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
     method public void setDistinct(boolean);
+    method public void setProjectionGreylist(@Nullable java.util.Collection<java.util.regex.Pattern>);
     method public void setProjectionMap(@Nullable java.util.Map<java.lang.String,java.lang.String>);
     method public void setStrict(boolean);
     method public void setStrictColumns(boolean);
@@ -23664,6 +23682,7 @@
     field public static final int TYPE_BUILTIN_EARPIECE = 1; // 0x1
     field public static final int TYPE_BUILTIN_MIC = 15; // 0xf
     field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2
+    field public static final int TYPE_BUILTIN_SPEAKER_SAFE = 24; // 0x18
     field public static final int TYPE_BUS = 21; // 0x15
     field public static final int TYPE_DOCK = 13; // 0xd
     field public static final int TYPE_FM = 14; // 0xe
@@ -25798,8 +25817,8 @@
     method @Nullable public android.graphics.Bitmap getImageAtIndex(int);
     method @Nullable public android.graphics.Bitmap getPrimaryImage(@NonNull android.media.MediaMetadataRetriever.BitmapParams);
     method @Nullable public android.graphics.Bitmap getPrimaryImage();
-    method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
-    method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
+    method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, @IntRange(from=1) int, @IntRange(from=1) int);
+    method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, @IntRange(from=1) int, @IntRange(from=1) int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
     method public void release();
     method public void setDataSource(String) throws java.lang.IllegalArgumentException;
     method public void setDataSource(String, java.util.Map<java.lang.String,java.lang.String>) throws java.lang.IllegalArgumentException;
@@ -26297,6 +26316,59 @@
     field public static final int SURFACE = 2; // 0x2
   }
 
+  public final class MediaRoute2Info implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getConnectionState();
+    method @Nullable public CharSequence getDescription();
+    method public int getDeviceType();
+    method @Nullable public android.os.Bundle getExtras();
+    method @NonNull public java.util.List<java.lang.String> getFeatures();
+    method @Nullable public android.net.Uri getIconUri();
+    method @NonNull public String getId();
+    method @NonNull public CharSequence getName();
+    method public int getVolume();
+    method public int getVolumeHandling();
+    method public int getVolumeMax();
+    method public boolean hasAnyFeatures(@NonNull java.util.Collection<java.lang.String>);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
+    field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
+    field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.MediaRoute2Info> CREATOR;
+    field public static final int DEVICE_TYPE_BLUETOOTH = 3; // 0x3
+    field public static final int DEVICE_TYPE_REMOTE_SPEAKER = 2; // 0x2
+    field public static final int DEVICE_TYPE_REMOTE_TV = 1; // 0x1
+    field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0
+    field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
+    field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+  }
+
+  public static final class MediaRoute2Info.Builder {
+    ctor public MediaRoute2Info.Builder(@NonNull String, @NonNull CharSequence);
+    ctor public MediaRoute2Info.Builder(@NonNull android.media.MediaRoute2Info);
+    method @NonNull public android.media.MediaRoute2Info.Builder addFeature(@NonNull String);
+    method @NonNull public android.media.MediaRoute2Info.Builder addFeatures(@NonNull java.util.Collection<java.lang.String>);
+    method @NonNull public android.media.MediaRoute2Info build();
+    method @NonNull public android.media.MediaRoute2Info.Builder clearFeatures();
+    method @NonNull public android.media.MediaRoute2Info.Builder setClientPackageName(@Nullable String);
+    method @NonNull public android.media.MediaRoute2Info.Builder setConnectionState(int);
+    method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
+    method @NonNull public android.media.MediaRoute2Info.Builder setDeviceType(int);
+    method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
+    method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
+    method @NonNull public android.media.MediaRoute2Info.Builder setVolume(int);
+    method @NonNull public android.media.MediaRoute2Info.Builder setVolumeHandling(int);
+    method @NonNull public android.media.MediaRoute2Info.Builder setVolumeMax(int);
+  }
+
+  public abstract class MediaRoute2ProviderService extends android.app.Service {
+    ctor public MediaRoute2ProviderService();
+    method public final void notifyRoutes(@NonNull java.util.Collection<android.media.MediaRoute2Info>);
+    method @NonNull public android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public void onDiscoveryPreferenceChanged(@NonNull android.media.RouteDiscoveryPreference);
+    field public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
+  }
+
   public class MediaRouter {
     method public void addCallback(int, android.media.MediaRouter.Callback);
     method public void addCallback(int, android.media.MediaRouter.Callback, int);
@@ -26420,6 +26492,20 @@
     method public abstract void onVolumeUpdateRequest(android.media.MediaRouter.RouteInfo, int);
   }
 
+  public class MediaRouter2 {
+    method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context);
+    method @NonNull public java.util.List<android.media.MediaRoute2Info> getRoutes();
+    method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference);
+    method public void unregisterRouteCallback(@NonNull android.media.MediaRouter2.RouteCallback);
+  }
+
+  public static class MediaRouter2.RouteCallback {
+    ctor public MediaRouter2.RouteCallback();
+    method public void onRoutesAdded(@NonNull java.util.List<android.media.MediaRoute2Info>);
+    method public void onRoutesChanged(@NonNull java.util.List<android.media.MediaRoute2Info>);
+    method public void onRoutesRemoved(@NonNull java.util.List<android.media.MediaRoute2Info>);
+  }
+
   public class MediaScannerConnection implements android.content.ServiceConnection {
     ctor public MediaScannerConnection(android.content.Context, android.media.MediaScannerConnection.MediaScannerConnectionClient);
     method public void connect();
@@ -26778,6 +26864,22 @@
     field public static final int URI_COLUMN_INDEX = 2; // 0x2
   }
 
+  public final class RouteDiscoveryPreference implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<java.lang.String> getPreferredFeatures();
+    method public boolean isActiveScan();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteDiscoveryPreference> CREATOR;
+  }
+
+  public static final class RouteDiscoveryPreference.Builder {
+    ctor public RouteDiscoveryPreference.Builder(@NonNull java.util.List<java.lang.String>, boolean);
+    ctor public RouteDiscoveryPreference.Builder(@NonNull android.media.RouteDiscoveryPreference);
+    method @NonNull public android.media.RouteDiscoveryPreference build();
+    method @NonNull public android.media.RouteDiscoveryPreference.Builder setActiveScan(boolean);
+    method @NonNull public android.media.RouteDiscoveryPreference.Builder setPreferredFeatures(@NonNull java.util.List<java.lang.String>);
+  }
+
   public final class Session2Command implements android.os.Parcelable {
     ctor public Session2Command(int);
     ctor public Session2Command(@NonNull String, @Nullable android.os.Bundle);
@@ -28700,7 +28802,9 @@
     method public int getVideoWidth();
     method public boolean isAudioDescription();
     method public boolean isEncrypted();
-    method public void writeToParcel(android.os.Parcel, int);
+    method public boolean isHardOfHearing();
+    method public boolean isSpokenSubtitle();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TvTrackInfo> CREATOR;
     field public static final int TYPE_AUDIO = 0; // 0x0
     field public static final int TYPE_SUBTITLE = 2; // 0x2
@@ -28709,19 +28813,21 @@
 
   public static final class TvTrackInfo.Builder {
     ctor public TvTrackInfo.Builder(int, @NonNull String);
-    method public android.media.tv.TvTrackInfo build();
-    method public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int);
+    method @NonNull public android.media.tv.TvTrackInfo build();
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int);
     method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioDescription(boolean);
-    method public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
-    method public android.media.tv.TvTrackInfo.Builder setDescription(CharSequence);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setDescription(@NonNull CharSequence);
     method @NonNull public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean);
-    method public android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle);
-    method public android.media.tv.TvTrackInfo.Builder setLanguage(String);
-    method public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte);
-    method public android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float);
-    method public android.media.tv.TvTrackInfo.Builder setVideoHeight(int);
-    method public android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float);
-    method public android.media.tv.TvTrackInfo.Builder setVideoWidth(int);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setExtra(@NonNull android.os.Bundle);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setHardOfHearing(boolean);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setLanguage(@NonNull String);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setSpokenSubtitle(boolean);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoHeight(int);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoWidth(int);
   }
 
   public class TvView extends android.view.ViewGroup {
@@ -29028,6 +29134,37 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortal> CREATOR;
   }
 
+  public class ConnectivityDiagnosticsManager {
+    method public void registerConnectivityDiagnosticsCallback(@NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback);
+    method public void unregisterConnectivityDiagnosticsCallback(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback);
+    field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1
+    field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2
+  }
+
+  public abstract static class ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback {
+    ctor public ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback();
+    method public void onConnectivityReport(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityReport);
+    method public void onDataStallSuspected(@NonNull android.net.ConnectivityDiagnosticsManager.DataStallReport);
+    method public void onNetworkConnectivityReported(@NonNull android.net.Network, boolean);
+  }
+
+  public static class ConnectivityDiagnosticsManager.ConnectivityReport {
+    ctor public ConnectivityDiagnosticsManager.ConnectivityReport(@NonNull android.net.Network, long, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle);
+    field @NonNull public final android.os.PersistableBundle additionalInfo;
+    field @NonNull public final android.net.LinkProperties linkProperties;
+    field @NonNull public final android.net.Network network;
+    field @NonNull public final android.net.NetworkCapabilities networkCapabilities;
+    field public final long reportTimestamp;
+  }
+
+  public static class ConnectivityDiagnosticsManager.DataStallReport {
+    ctor public ConnectivityDiagnosticsManager.DataStallReport(@NonNull android.net.Network, long, int, @NonNull android.os.PersistableBundle);
+    field public final int detectionMethod;
+    field @NonNull public final android.net.Network network;
+    field public final long reportTimestamp;
+    field @NonNull public final android.os.PersistableBundle stallDetails;
+  }
+
   public class ConnectivityManager {
     method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
     method public boolean bindProcessToNetwork(@Nullable android.net.Network);
@@ -29132,6 +29269,7 @@
     ctor public DhcpInfo();
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.DhcpInfo> CREATOR;
     field public int dns1;
     field public int dns2;
     field public int gateway;
@@ -29498,6 +29636,7 @@
     method public android.net.NetworkRequest.Builder addCapability(int);
     method public android.net.NetworkRequest.Builder addTransportType(int);
     method public android.net.NetworkRequest build();
+    method @NonNull public android.net.NetworkRequest.Builder clearCapabilities();
     method public android.net.NetworkRequest.Builder removeCapability(int);
     method public android.net.NetworkRequest.Builder removeTransportType(int);
     method public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
@@ -30296,6 +30435,7 @@
   @Deprecated public static class WifiConfiguration.AuthAlgorithm {
     field @Deprecated public static final int LEAP = 2; // 0x2
     field @Deprecated public static final int OPEN = 0; // 0x0
+    field @Deprecated public static final int SAE = 3; // 0x3
     field @Deprecated public static final int SHARED = 1; // 0x1
     field @Deprecated public static final String[] strings;
     field @Deprecated public static final String varName = "auth_alg";
@@ -30504,6 +30644,7 @@
     field public static final String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
     field public static final String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
     field public static final String ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION = "android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION";
+    field public static final String ACTION_WIFI_SCAN_AVAILABLE = "android.net.wifi.action.WIFI_SCAN_AVAILABLE";
     field @Deprecated public static final int ERROR_AUTHENTICATING = 1; // 0x1
     field @Deprecated public static final String EXTRA_BSSID = "bssid";
     field public static final String EXTRA_NETWORK_INFO = "networkInfo";
@@ -30512,6 +30653,7 @@
     field @Deprecated public static final String EXTRA_NEW_STATE = "newState";
     field public static final String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state";
     field public static final String EXTRA_RESULTS_UPDATED = "resultsUpdated";
+    field public static final String EXTRA_SCAN_AVAILABLE = "android.net.wifi.extra.SCAN_AVAILABLE";
     field @Deprecated public static final String EXTRA_SUPPLICANT_CONNECTED = "connected";
     field @Deprecated public static final String EXTRA_SUPPLICANT_ERROR = "supplicantError";
     field @Deprecated public static final String EXTRA_WIFI_INFO = "wifiInfo";
@@ -30628,11 +30770,11 @@
     ctor public WifiNetworkSuggestion.Builder();
     method @NonNull public android.net.wifi.WifiNetworkSuggestion build();
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setBssid(@NonNull android.net.MacAddress);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setCredentialSharedWithUser(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsAppInteractionRequired(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsEnhancedOpen(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsHiddenSsid(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsMetered(boolean);
-    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsUserAllowedToManuallyConnect(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsUserInteractionRequired(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPasspointConfig(@NonNull android.net.wifi.hotspot2.PasspointConfiguration);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriority(@IntRange(from=0) int);
@@ -35698,7 +35840,9 @@
     method public int describeContents();
     method @Nullable public android.os.PersistableBundle getPersistableBundle(@Nullable String);
     method public void putPersistableBundle(@Nullable String, @Nullable android.os.PersistableBundle);
+    method @NonNull public static android.os.PersistableBundle readFromStream(@NonNull java.io.InputStream) throws java.io.IOException;
     method public void writeToParcel(android.os.Parcel, int);
+    method public void writeToStream(@NonNull java.io.OutputStream) throws java.io.IOException;
     field @NonNull public static final android.os.Parcelable.Creator<android.os.PersistableBundle> CREATOR;
     field public static final android.os.PersistableBundle EMPTY;
   }
@@ -35800,6 +35944,7 @@
     field public static final int THREAD_PRIORITY_URGENT_AUDIO = -19; // 0xffffffed
     field public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 0xfffffff8
     field public static final int THREAD_PRIORITY_VIDEO = -10; // 0xfffffff6
+    field public static final int WIFI_UID = 1010; // 0x3f2
   }
 
   public abstract class ProxyFileDescriptorCallback {
@@ -36034,6 +36179,7 @@
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
     method public boolean hasUserRestriction(String);
     method public boolean isDemoUser();
+    method public boolean isManagedProfile();
     method public boolean isQuietModeEnabled(android.os.UserHandle);
     method public boolean isSystemUser();
     method public boolean isUserAGoat();
@@ -36366,15 +36512,22 @@
     method public boolean isObbMounted(String);
     method public boolean mountObb(String, String, android.os.storage.OnObbStateChangeListener);
     method @NonNull public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler) throws java.io.IOException;
+    method public void registerStorageVolumeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.os.storage.StorageManager.StorageVolumeCallback);
     method public void setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException;
     method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
     method public boolean unmountObb(String, boolean, android.os.storage.OnObbStateChangeListener);
+    method public void unregisterStorageVolumeCallback(@NonNull android.os.storage.StorageManager.StorageVolumeCallback);
     field public static final String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
     field public static final String EXTRA_REQUESTED_BYTES = "android.os.storage.extra.REQUESTED_BYTES";
     field public static final String EXTRA_UUID = "android.os.storage.extra.UUID";
     field public static final java.util.UUID UUID_DEFAULT;
   }
 
+  public static class StorageManager.StorageVolumeCallback {
+    ctor public StorageManager.StorageVolumeCallback();
+    method public void onStateChanged(@NonNull android.os.storage.StorageVolume);
+  }
+
   public final class StorageVolume implements android.os.Parcelable {
     method @Deprecated @Nullable public android.content.Intent createAccessIntent(String);
     method @NonNull public android.content.Intent createOpenDocumentTreeIntent();
@@ -39295,6 +39448,7 @@
     field @Deprecated public static final String LONGITUDE = "longitude";
     field @Deprecated public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
     field @Deprecated public static final String PICASA_ID = "picasa_id";
+    field public static final String SCENE_CAPTURE_TYPE = "scene_capture_type";
   }
 
   public static final class MediaStore.Images.Media implements android.provider.MediaStore.Images.ImageColumns {
@@ -39490,7 +39644,9 @@
     field public static final String ACTION_LOCALE_SETTINGS = "android.settings.LOCALE_SETTINGS";
     field public static final String ACTION_LOCATION_SOURCE_SETTINGS = "android.settings.LOCATION_SOURCE_SETTINGS";
     field public static final String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS";
+    field public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION";
     field public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS";
+    field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
     field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS";
     field public static final String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION";
     field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES";
@@ -42634,11 +42790,20 @@
     method public android.content.Intent createEnrollIntent();
     method public android.content.Intent createReEnrollIntent();
     method public android.content.Intent createUnEnrollIntent();
+    method public int getParameter(int);
+    method public int getSupportedAudioCapabilities();
     method public int getSupportedRecognitionModes();
+    method @Nullable public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int);
+    method public int setParameter(int, int);
     method public boolean startRecognition(int);
     method public boolean stopRecognition();
+    field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
+    field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
+    field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
     field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
     field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
+    field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4
+    field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8
     field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2
     field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1
     field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe
@@ -42661,6 +42826,11 @@
     method @Nullable public byte[] getTriggerAudio();
   }
 
+  public static final class AlwaysOnHotwordDetector.ModelParamRange {
+    method public int end();
+    method public int start();
+  }
+
   public class VoiceInteractionService extends android.app.Service {
     ctor public VoiceInteractionService();
     method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
@@ -44976,6 +45146,7 @@
     field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
     field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
     field public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1; // 0xffffffff
+    field public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool";
     field public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
     field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
     field public static final String KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrp_thresholds_int_array";
@@ -44991,7 +45162,7 @@
     field public static final String KEY_ALLOW_LOCAL_DTMF_TONES_BOOL = "allow_local_dtmf_tones_bool";
     field public static final String KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL = "allow_merge_wifi_calls_when_vowifi_off_bool";
     field public static final String KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL = "allow_non_emergency_calls_in_ecm_bool";
-    field public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool";
+    field @Deprecated public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool";
     field public static final String KEY_APN_EXPAND_BOOL = "apn_expand_bool";
     field public static final String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool";
     field public static final String KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL = "call_barring_supports_deactivate_all_bool";
@@ -45173,6 +45344,7 @@
 
   public static final class CarrierConfigManager.Ims {
     field public static final String KEY_PREFIX = "ims.";
+    field public static final String KEY_WIFI_OFF_DEFERRING_TIME_INT = "ims.wifi_off_deferring_time_int";
   }
 
   public abstract class CellIdentity implements android.os.Parcelable {
@@ -45481,6 +45653,11 @@
     method @Nullable public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, @NonNull java.util.concurrent.Executor, android.telephony.mbms.StreamingServiceCallback);
   }
 
+  public class MmsManager {
+    method public void downloadMultimediaMessage(int, @NonNull String, @NonNull android.net.Uri, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
+    method public void sendMultimediaMessage(int, @NonNull android.net.Uri, @Nullable String, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
+  }
+
   @Deprecated public class NeighboringCellInfo implements android.os.Parcelable {
     ctor @Deprecated public NeighboringCellInfo();
     ctor @Deprecated public NeighboringCellInfo(int, int);
@@ -45677,6 +45854,7 @@
     method public String getOperatorNumeric();
     method public boolean getRoaming();
     method public int getState();
+    method public boolean isSearching();
     method public void setIsManualSelection(boolean);
     method public void setOperatorName(String, String, String);
     method public void setRoaming(boolean);
@@ -45717,8 +45895,8 @@
     method public String createAppSpecificSmsToken(android.app.PendingIntent);
     method @Nullable public String createAppSpecificSmsTokenWithPackageInfo(@Nullable String, @NonNull android.app.PendingIntent);
     method public java.util.ArrayList<java.lang.String> divideMessage(String);
-    method public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent);
-    method @Nullable public android.os.Bundle getCarrierConfigValues();
+    method @Deprecated public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent);
+    method @NonNull public android.os.Bundle getCarrierConfigValues();
     method public static android.telephony.SmsManager getDefault();
     method public static int getDefaultSmsSubscriptionId();
     method public static android.telephony.SmsManager getSmsManagerForSubscriptionId(int);
@@ -45727,7 +45905,7 @@
     method public int getSubscriptionId();
     method public void injectSmsPdu(byte[], String, android.app.PendingIntent);
     method public void sendDataMessage(String, String, short, byte[], android.app.PendingIntent, android.app.PendingIntent);
-    method public void sendMultimediaMessage(android.content.Context, android.net.Uri, String, android.os.Bundle, android.app.PendingIntent);
+    method @Deprecated public void sendMultimediaMessage(android.content.Context, android.net.Uri, String, android.os.Bundle, android.app.PendingIntent);
     method public void sendMultipartTextMessage(String, String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>);
     method public void sendTextMessage(String, String, String, android.app.PendingIntent, android.app.PendingIntent);
     method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.SEND_SMS}) public void sendTextMessageWithoutPersisting(String, String, String, android.app.PendingIntent, android.app.PendingIntent);
@@ -45935,6 +46113,7 @@
   public class SubscriptionManager {
     method public void addOnOpportunisticSubscriptionsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
     method public void addOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
+    method public void addOnSubscriptionsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void addSubscriptionsIntoGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
     method public boolean canManageSubscription(android.telephony.SubscriptionInfo);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.os.ParcelUuid createSubscriptionGroup(@NonNull java.util.List<java.lang.Integer>);
@@ -46093,12 +46272,12 @@
     method public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle);
     method public boolean hasCarrierPrivileges();
     method public boolean hasIccCard();
-    method public boolean iccCloseLogicalChannel(int);
-    method public byte[] iccExchangeSimIO(int, int, int, int, int, String);
+    method @Deprecated public boolean iccCloseLogicalChannel(int);
+    method @Deprecated public byte[] iccExchangeSimIO(int, int, int, int, int, String);
     method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String);
-    method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int);
-    method public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
-    method public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
+    method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int);
+    method @Deprecated public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
+    method @Deprecated public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
     method public boolean isConcurrentVoiceAndDataSupported();
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean isDataEnabled();
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled();
@@ -46116,7 +46295,7 @@
     method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback);
     method public void sendDialerSpecialCode(String);
-    method public String sendEnvelopeWithStatus(String);
+    method @Deprecated public String sendEnvelopeWithStatus(String);
     method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler);
     method public void sendVisualVoicemailSms(String, int, String, android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean);
@@ -51558,9 +51737,10 @@
     method public boolean dispatchUnhandledMove(android.view.View, int);
     method protected void dispatchVisibilityChanged(@NonNull android.view.View, int);
     method public void dispatchWindowFocusChanged(boolean);
-    method public void dispatchWindowInsetsAnimationFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+    method public void dispatchWindowInsetsAnimationFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+    method public void dispatchWindowInsetsAnimationPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
     method @NonNull public android.view.WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull android.view.WindowInsets);
-    method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
+    method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
     method public void dispatchWindowSystemUiVisiblityChanged(int);
     method public void dispatchWindowVisibilityChanged(int);
     method @CallSuper public void draw(android.graphics.Canvas);
@@ -53240,9 +53420,10 @@
   }
 
   public interface WindowInsetsAnimationCallback {
-    method public default void onFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+    method public default void onFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+    method public default void onPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
     method @NonNull public android.view.WindowInsets onProgress(@NonNull android.view.WindowInsets);
-    method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
+    method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
   }
 
   public static final class WindowInsetsAnimationCallback.AnimationBounds {
diff --git a/api/module-app-current.txt b/api/module-app-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-app-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/module-app-removed.txt b/api/module-app-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-app-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-lib-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/module-lib-removed.txt b/api/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/system-current.txt b/api/system-current.txt
index d768c02..87af55c 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -152,6 +152,7 @@
     field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
     field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
     field public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES";
+    field public static final String RADIO_SCAN_WITHOUT_LOCATION = "android.permission.RADIO_SCAN_WITHOUT_LOCATION";
     field public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission.READ_ACTIVE_EMERGENCY_SESSION";
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
     field public static final String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS";
@@ -189,6 +190,7 @@
     field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES";
     field public static final String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS";
     field public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
+    field public static final String SECURE_ELEMENT_PRIVILEGED = "android.permission.SECURE_ELEMENT_PRIVILEGED";
     field public static final String SEND_DEVICE_CUSTOMIZATION_READY = "android.permission.SEND_DEVICE_CUSTOMIZATION_READY";
     field public static final String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS";
     field public static final String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION";
@@ -240,7 +242,6 @@
     field public static final int isVrOnly = 16844152; // 0x1010578
     field public static final int requiredSystemPropertyName = 16844133; // 0x1010565
     field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566
-    field public static final int resourcesMap = 16844297; // 0x1010609
     field public static final int supportsAmbientMode = 16844173; // 0x101058d
     field public static final int userRestriction = 16844164; // 0x1010584
   }
@@ -282,6 +283,7 @@
     field public static final int config_helpIntentNameKey = 17039390; // 0x104001e
     field public static final int config_helpPackageNameKey = 17039387; // 0x104001b
     field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
+    field public static final int config_systemGallery = 17039402; // 0x104002a
   }
 
   public static final class R.style {
@@ -567,6 +569,7 @@
   }
 
   public class DownloadManager {
+    method @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public void onMediaStoreDownloadsDeleted(@NonNull android.util.LongSparseArray<java.lang.String>);
     field public static final String ACTION_DOWNLOAD_COMPLETED = "android.intent.action.DOWNLOAD_COMPLETED";
   }
 
@@ -677,6 +680,7 @@
   public class StatusBarManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo();
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean);
+    method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean);
   }
 
   public static final class StatusBarManager.DisableInfo {
@@ -799,6 +803,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedKiosk();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isOrganizationOwnedDeviceWithManagedProfile();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUnattendedManagedKiosk();
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long);
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean);
@@ -809,6 +814,7 @@
     field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
     field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
     field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION";
+    field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
     field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
     field public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
     field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
@@ -1327,6 +1333,10 @@
     field public static final String SERVICE_INTERFACE = "android.app.usage.CacheQuotaService";
   }
 
+  public class NetworkStatsManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.net.netstats.provider.NetworkStatsProviderCallback registerNetworkStatsProvider(@NonNull String, @NonNull android.net.netstats.provider.AbstractNetworkStatsProvider);
+  }
+
   public static final class UsageEvents.Event {
     method public int getInstanceId();
     method @Nullable public String getNotificationChannelId();
@@ -1533,6 +1543,10 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
   }
 
+  public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+  }
+
   public final class BluetoothHidHost implements android.bluetooth.BluetoothProfile {
     method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
@@ -1541,12 +1555,20 @@
     field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
   }
 
+  public final class BluetoothMap implements android.bluetooth.BluetoothProfile {
+    method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int);
+    field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
+  }
+
   public final class BluetoothPan implements android.bluetooth.BluetoothProfile {
     method protected void finalize();
     method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
     method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
     method public boolean isTetheringOn();
     method public void setBluetoothTethering(boolean);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
     field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
     field public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
     field public static final int LOCAL_NAP_ROLE = 1; // 0x1
@@ -1653,17 +1675,22 @@
     method @NonNull public final android.os.UserHandle getSendingUser();
   }
 
+  public abstract class ContentProvider implements android.content.ComponentCallbacks2 {
+    method public int checkUriPermission(@NonNull android.net.Uri, int, int);
+  }
+
   public class ContentProviderClient implements java.lang.AutoCloseable {
     method @RequiresPermission(android.Manifest.permission.REMOVE_TASKS) public void setDetectNotResponding(long);
   }
 
   public abstract class ContentResolver {
+    method @NonNull public static android.net.Uri decodeFromFile(@NonNull java.io.File);
+    method @NonNull public static java.io.File encodeToFile(@NonNull android.net.Uri);
     method @Nullable @RequiresPermission("android.permission.CACHE_CONTENT") public android.os.Bundle getCache(@NonNull android.net.Uri);
     method @RequiresPermission("android.permission.CACHE_CONTENT") public void putCache(@NonNull android.net.Uri, @Nullable android.os.Bundle);
   }
 
   public abstract class Context {
-    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean bindServiceAsUser(@RequiresPermission android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle);
     method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
     method public abstract android.content.Context createCredentialProtectedStorageContext();
     method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -1698,6 +1725,7 @@
     field public static final String TELEPHONY_REGISTRY_SERVICE = "telephony_registry";
     field public static final String TETHERING_SERVICE = "tethering";
     field public static final String VR_SERVICE = "vrmanager";
+    field public static final String WIFI_COND_SERVICE = "wificond";
     field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
     field public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
   }
@@ -1771,6 +1799,7 @@
     field @Deprecated public static final String EXTRA_SIM_STATE = "ss";
     field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP";
     field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE";
+    field public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 67108864; // 0x4000000
     field public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION";
     field @Deprecated public static final String SIM_ABSENT_ON_PERM_DISABLED = "PERM_DISABLED";
     field @Deprecated public static final String SIM_LOCKED_NETWORK = "NETWORK";
@@ -3536,7 +3565,6 @@
   }
 
   public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable {
-    method public int describeContents();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModelParamRange> CREATOR;
     field public final int end;
@@ -3546,7 +3574,10 @@
   public static final class SoundTrigger.ModuleProperties implements android.os.Parcelable {
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
+    field public static final int CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> CREATOR;
+    field public final int audioCapabilities;
     field @NonNull public final String description;
     field public final int id;
     field @NonNull public final String implementor;
@@ -3557,6 +3588,7 @@
     field public final int powerConsumptionMw;
     field public final int recognitionModes;
     field public final boolean returnsTriggerInEvent;
+    field @NonNull public final String supportedModelArch;
     field public final boolean supportsCaptureTransition;
     field public final boolean supportsConcurrentCapture;
     field @NonNull public final java.util.UUID uuid;
@@ -3579,9 +3611,17 @@
   }
 
   public class UsbManager {
+    method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public long getCurrentFunctions();
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_USB) public java.util.List<android.hardware.usb.UsbPort> getPorts();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void grantPermission(android.hardware.usb.UsbDevice, String);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setCurrentFunctions(long);
     field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_CHANGED = "android.hardware.usb.action.USB_PORT_CHANGED";
+    field public static final String ACTION_USB_STATE = "android.hardware.usb.action.USB_STATE";
+    field public static final long FUNCTION_NONE = 0L; // 0x0L
+    field public static final long FUNCTION_RNDIS = 32L; // 0x20L
+    field public static final String USB_CONFIGURED = "configured";
+    field public static final String USB_CONNECTED = "connected";
+    field public static final String USB_FUNCTION_RNDIS = "rndis";
   }
 
   public final class UsbPort {
@@ -4034,6 +4074,10 @@
     field public static final int ROLE_OUTPUT = 2; // 0x2
   }
 
+  public final class AudioDeviceInfo {
+    field public static final int TYPE_REMOTE_SUBMIX = 25; // 0x19
+  }
+
   public final class AudioFocusInfo implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.media.AudioAttributes getAttributes();
@@ -4061,6 +4105,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies();
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAddress> getDevicesForAttributes(@NonNull android.media.AudioAttributes);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAddress getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
@@ -4294,7 +4339,7 @@
   }
 
   public static interface MediaSessionManager.OnMediaKeyEventDispatchedListener {
-    method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @NonNull android.media.session.MediaSession.Token);
+    method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @Nullable android.media.session.MediaSession.Token);
   }
 
   public static interface MediaSessionManager.OnMediaKeyEventSessionChangedListener {
@@ -4328,6 +4373,8 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public boolean stopRecognition();
     field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
     field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
+    field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4
+    field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8
   }
 
   public abstract static class SoundTriggerDetector.Callback {
@@ -4350,9 +4397,9 @@
     method public int getDetectionServiceOperationsTimeout();
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModelParamRange queryParameter(@Nullable java.util.UUID, int);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model);
   }
 
@@ -4577,6 +4624,28 @@
     method public abstract int getType();
   }
 
+  public class Lnb implements java.lang.AutoCloseable {
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void close();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int sendDiseqcMessage(@NonNull byte[]);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setSatellitePosition(int);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setTone(int);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setVoltage(int);
+    field public static final int POSITION_A = 1; // 0x1
+    field public static final int POSITION_B = 2; // 0x2
+    field public static final int POSITION_UNDEFINED = 0; // 0x0
+    field public static final int TONE_CONTINUOUS = 1; // 0x1
+    field public static final int TONE_NONE = 0; // 0x0
+    field public static final int VOLTAGE_11V = 2; // 0x2
+    field public static final int VOLTAGE_12V = 3; // 0x3
+    field public static final int VOLTAGE_13V = 4; // 0x4
+    field public static final int VOLTAGE_14V = 5; // 0x5
+    field public static final int VOLTAGE_15V = 6; // 0x6
+    field public static final int VOLTAGE_18V = 7; // 0x7
+    field public static final int VOLTAGE_19V = 8; // 0x8
+    field public static final int VOLTAGE_5V = 1; // 0x1
+    field public static final int VOLTAGE_NONE = 0; // 0x0
+  }
+
   public final class Tuner implements java.lang.AutoCloseable {
     ctor public Tuner(@NonNull android.content.Context);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Tuner.Descrambler openDescrambler();
@@ -4599,6 +4668,13 @@
     field public static final int FILTER_STATUS_HIGH_WATER = 4; // 0x4
     field public static final int FILTER_STATUS_LOW_WATER = 2; // 0x2
     field public static final int FILTER_STATUS_OVERFLOW = 8; // 0x8
+    field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
+    field public static final int RESULT_INVALID_STATE = 3; // 0x3
+    field public static final int RESULT_NOT_INITIALIZED = 2; // 0x2
+    field public static final int RESULT_OUT_OF_MEMORY = 5; // 0x5
+    field public static final int RESULT_SUCCESS = 0; // 0x0
+    field public static final int RESULT_UNAVAILABLE = 1; // 0x1
+    field public static final int RESULT_UNKNOWN_ERROR = 6; // 0x6
   }
 
 }
@@ -4678,6 +4754,7 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl();
     method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public int registerNetworkProvider(@NonNull android.net.NetworkProvider);
     method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi();
@@ -4685,6 +4762,7 @@
     method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback);
     method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler);
     method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider);
     method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
     field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
     field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
@@ -4824,6 +4902,7 @@
   public final class MatchAllNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
     ctor public MatchAllNetworkSpecifier();
     method public int describeContents();
+    method public boolean satisfiedBy(android.net.NetworkSpecifier);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.MatchAllNetworkSpecifier> CREATOR;
   }
@@ -4835,6 +4914,7 @@
   }
 
   public final class NetworkCapabilities implements android.os.Parcelable {
+    method public boolean deduceRestrictedCapability();
     method @NonNull public int[] getTransportTypes();
     method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
     method @NonNull public android.net.NetworkCapabilities setSSID(@Nullable String);
@@ -4854,6 +4934,17 @@
     field public final android.net.WifiKey wifiKey;
   }
 
+  public class NetworkProvider {
+    ctor public NetworkProvider(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void declareNetworkRequestUnfulfillable(@NonNull android.net.NetworkRequest);
+    method @Nullable public android.os.Messenger getMessenger();
+    method @NonNull public String getName();
+    method public int getProviderId();
+    method public void onNetworkRequested(@NonNull android.net.NetworkRequest, int, int);
+    method public void onRequestWithdrawn(@NonNull android.net.NetworkRequest);
+    field public static final int ID_NONE = -1; // 0xffffffff
+  }
+
   public abstract class NetworkRecommendationProvider {
     ctor public NetworkRecommendationProvider(android.content.Context, java.util.concurrent.Executor);
     method public final android.os.IBinder getBinder();
@@ -4887,10 +4978,43 @@
     method public void updateScores(@NonNull java.util.List<android.net.ScoredNetwork>);
   }
 
+  public abstract class NetworkSpecifier {
+    method public void assertValidFromUid(int);
+    method @Nullable public android.net.NetworkSpecifier redact();
+    method public abstract boolean satisfiedBy(@Nullable android.net.NetworkSpecifier);
+  }
+
   public class NetworkStack {
     field public static final String PERMISSION_MAINLINE_NETWORK_STACK = "android.permission.MAINLINE_NETWORK_STACK";
   }
 
+  public final class NetworkStats implements android.os.Parcelable {
+    ctor public NetworkStats(long, int);
+    method @NonNull public android.net.NetworkStats add(@NonNull android.net.NetworkStats);
+    method @NonNull public android.net.NetworkStats addValues(@NonNull android.net.NetworkStats.Entry);
+    method public int describeContents();
+    method @NonNull public android.net.NetworkStats subtract(@NonNull android.net.NetworkStats);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStats> CREATOR;
+    field public static final int DEFAULT_NETWORK_NO = 0; // 0x0
+    field public static final int DEFAULT_NETWORK_YES = 1; // 0x1
+    field @Nullable public static final String IFACE_ALL;
+    field public static final String IFACE_VT = "vt_data0";
+    field public static final int METERED_NO = 0; // 0x0
+    field public static final int METERED_YES = 1; // 0x1
+    field public static final int ROAMING_NO = 0; // 0x0
+    field public static final int ROAMING_YES = 1; // 0x1
+    field public static final int SET_DEFAULT = 0; // 0x0
+    field public static final int SET_FOREGROUND = 1; // 0x1
+    field public static final int TAG_NONE = 0; // 0x0
+    field public static final int UID_ALL = -1; // 0xffffffff
+    field public static final int UID_TETHERING = -5; // 0xfffffffb
+  }
+
+  public static class NetworkStats.Entry {
+    ctor public NetworkStats.Entry(@Nullable String, int, int, int, int, int, int, long, long, long, long, long);
+  }
+
   public final class RouteInfo implements android.os.Parcelable {
     ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int);
     method public int getType();
@@ -4957,6 +5081,7 @@
   public final class StringNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
     ctor public StringNetworkSpecifier(@NonNull String);
     method public int describeContents();
+    method public boolean satisfiedBy(android.net.NetworkSpecifier);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.StringNetworkSpecifier> CREATOR;
     field @NonNull public final String specifier;
@@ -5481,6 +5606,25 @@
 
 }
 
+package android.net.netstats.provider {
+
+  public abstract class AbstractNetworkStatsProvider {
+    ctor public AbstractNetworkStatsProvider();
+    method public abstract void requestStatsUpdate(int);
+    method public abstract void setAlert(long);
+    method public abstract void setLimit(@NonNull String, long);
+    field public static final int QUOTA_UNLIMITED = -1; // 0xffffffff
+  }
+
+  public class NetworkStatsProviderCallback {
+    method public void onAlertReached();
+    method public void onLimitReached();
+    method public void onStatsUpdated(int, @NonNull android.net.NetworkStats, @NonNull android.net.NetworkStats);
+    method public void unregister();
+  }
+
+}
+
 package android.net.util {
 
   public final class SocketUtils {
@@ -5740,17 +5884,22 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApCapability> CREATOR;
     field public static final int SOFTAP_FEATURE_ACS_OFFLOAD = 1; // 0x1
     field public static final int SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT = 2; // 0x2
+    field public static final int SOFTAP_FEATURE_WPA3_SAE = 4; // 0x4
   }
 
   public final class SoftApConfiguration implements android.os.Parcelable {
     method public int describeContents();
+    method @NonNull public java.util.List<android.net.MacAddress> getAllowedClientList();
     method public int getBand();
+    method @NonNull public java.util.List<android.net.MacAddress> getBlockedClientList();
     method @Nullable public android.net.MacAddress getBssid();
     method public int getChannel();
     method public int getMaxNumberOfClients();
+    method @Nullable public String getPassphrase();
     method public int getSecurityType();
+    method public int getShutdownTimeoutMillis();
     method @Nullable public String getSsid();
-    method @Nullable public String getWpa2Passphrase();
+    method public boolean isClientControlByUserEnabled();
     method public boolean isHiddenSsid();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int BAND_2GHZ = 1; // 0x1
@@ -5760,19 +5909,24 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApConfiguration> CREATOR;
     field public static final int SECURITY_TYPE_OPEN = 0; // 0x0
     field public static final int SECURITY_TYPE_WPA2_PSK = 1; // 0x1
+    field public static final int SECURITY_TYPE_WPA3_SAE = 3; // 0x3
+    field public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2; // 0x2
   }
 
   public static final class SoftApConfiguration.Builder {
     ctor public SoftApConfiguration.Builder();
     ctor public SoftApConfiguration.Builder(@NonNull android.net.wifi.SoftApConfiguration);
     method @NonNull public android.net.wifi.SoftApConfiguration build();
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder enableClientControlByUser(boolean);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBand(int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBssid(@Nullable android.net.MacAddress);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setChannel(int, int);
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setClientList(@NonNull java.util.List<android.net.MacAddress>, @NonNull java.util.List<android.net.MacAddress>);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(int);
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setPassphrase(@Nullable String, int);
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setShutdownTimeoutMillis(int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setSsid(@Nullable String);
-    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setWpa2Passphrase(@Nullable String);
   }
 
   public final class SoftApInfo implements android.os.Parcelable {
@@ -5915,6 +6069,7 @@
   public class WifiManager {
     method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void addOnWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener);
     method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoin(int, boolean);
+    method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoinPasspoint(@NonNull String, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(int, @Nullable android.net.wifi.WifiManager.ActionListener);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void disable(int, @Nullable android.net.wifi.WifiManager.ActionListener);
@@ -5934,6 +6089,7 @@
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void getWifiActivityEnergyInfoAsync(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiActivityEnergyInfoListener);
     method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState();
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(@NonNull java.util.List<android.net.wifi.ScanResult>);
     method public boolean isApMacRandomizationSupported();
     method public boolean isConnectedMacRandomizationSupported();
     method @Deprecated public boolean isDeviceToDeviceRttSupported();
@@ -5991,6 +6147,7 @@
     field public static final String EXTRA_OSU_NETWORK = "android.net.wifi.extra.OSU_NETWORK";
     field public static final String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state";
     field public static final String EXTRA_URL = "android.net.wifi.extra.URL";
+    field public static final String EXTRA_WIFI_AP_FAILURE_REASON = "android.net.wifi.extra.WIFI_AP_FAILURE_REASON";
     field public static final String EXTRA_WIFI_AP_INTERFACE_NAME = "android.net.wifi.extra.WIFI_AP_INTERFACE_NAME";
     field public static final String EXTRA_WIFI_AP_MODE = "android.net.wifi.extra.WIFI_AP_MODE";
     field public static final String EXTRA_WIFI_AP_STATE = "wifi_state";
@@ -6003,6 +6160,8 @@
     field public static final int IFACE_IP_MODE_UNSPECIFIED = -1; // 0xffffffff
     field public static final int PASSPOINT_HOME_NETWORK = 0; // 0x0
     field public static final int PASSPOINT_ROAMING_NETWORK = 1; // 0x1
+    field public static final int SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0; // 0x0
+    field public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1; // 0x1
     field public static final int SAP_START_FAILURE_GENERAL = 0; // 0x0
     field public static final int SAP_START_FAILURE_NO_CHANNEL = 1; // 0x1
     field public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2; // 0x2
@@ -6044,6 +6203,7 @@
   }
 
   public static interface WifiManager.SoftApCallback {
+    method public default void onBlockedClientConnecting(@NonNull android.net.wifi.WifiClient, int);
     method public default void onCapabilityChanged(@NonNull android.net.wifi.SoftApCapability);
     method public default void onConnectedClientsChanged(@NonNull java.util.List<android.net.wifi.WifiClient>);
     method public default void onInfoChanged(@NonNull android.net.wifi.SoftApInfo);
@@ -6069,10 +6229,33 @@
     field public int numUsage;
   }
 
+  public final class WifiNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+    method public boolean satisfiedBy(android.net.NetworkSpecifier);
+  }
+
   public static final class WifiNetworkSuggestion.Builder {
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int);
   }
 
+  public final class WifiOemConfigStoreMigrationHook {
+    method @Nullable public static android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData load();
+  }
+
+  public static final class WifiOemConfigStoreMigrationHook.MigrationData implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public java.util.List<android.net.wifi.WifiConfiguration> getUserSavedNetworkConfigurations();
+    method @Nullable public android.net.wifi.SoftApConfiguration getUserSoftApConfiguration();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData> CREATOR;
+  }
+
+  public static final class WifiOemConfigStoreMigrationHook.MigrationData.Builder {
+    ctor public WifiOemConfigStoreMigrationHook.MigrationData.Builder();
+    method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData build();
+    method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSavedNetworkConfigurations(@NonNull java.util.List<android.net.wifi.WifiConfiguration>);
+    method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration);
+  }
+
   public class WifiScanner {
     method @Deprecated public void configureWifiChange(int, int, int, int, int, android.net.wifi.WifiScanner.BssidInfo[]);
     method @Deprecated public void configureWifiChange(android.net.wifi.WifiScanner.WifiChangeSettings);
@@ -6255,6 +6438,10 @@
     method @Deprecated public android.net.NetworkSpecifier createNetworkSpecifierPmk(@NonNull android.net.wifi.aware.PeerHandle, @NonNull byte[]);
   }
 
+  public final class WifiAwareNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+    method public boolean satisfiedBy(android.net.NetworkSpecifier);
+  }
+
   public class WifiAwareSession implements java.lang.AutoCloseable {
     method public android.net.NetworkSpecifier createNetworkSpecifierPmk(int, @NonNull byte[], @NonNull byte[]);
   }
@@ -6271,6 +6458,10 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.OsuProvider> CREATOR;
   }
 
+  public final class PasspointConfiguration implements android.os.Parcelable {
+    method public boolean isAutoJoinEnabled();
+  }
+
   public abstract class ProvisioningCallback {
     ctor public ProvisioningCallback();
     method public abstract void onProvisioningComplete();
@@ -6937,6 +7128,10 @@
     method public boolean hasSingleFileDescriptor();
   }
 
+  public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable {
+    method @NonNull public static android.os.ParcelFileDescriptor wrap(@NonNull android.os.ParcelFileDescriptor, @NonNull android.os.Handler, @NonNull android.os.ParcelFileDescriptor.OnCloseListener) throws java.io.IOException;
+  }
+
   public final class PowerManager {
     method @RequiresPermission(allOf={android.Manifest.permission.READ_DREAM_STATE, android.Manifest.permission.WRITE_DREAM_STATE}) public void dream(long);
     method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public boolean forceSuspend();
@@ -7045,7 +7240,22 @@
   }
 
   public class TelephonyServiceManager {
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getCarrierConfigServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getEuiccCardControllerServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getEuiccControllerService();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getIccPhoneBookServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getNetworkPolicyServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getOpportunisticNetworkServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getPackageManagerServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getPermissionManagerServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getPhoneSubServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getSmsServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getSubscriptionServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyImsServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyRcsMessageServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyRegistryServiceRegisterer();
     method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyServiceRegisterer();
+    method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getWindowServiceRegisterer();
   }
 
   public static class TelephonyServiceManager.ServiceNotFoundException extends java.lang.Exception {
@@ -7061,11 +7271,13 @@
 
   public class UpdateEngine {
     ctor public UpdateEngine();
+    method @NonNull public android.os.UpdateEngine.AllocateSpaceResult allocateSpace(@NonNull String, @NonNull String[]);
     method public void applyPayload(String, long, long, String[]);
-    method public void applyPayload(@NonNull android.os.ParcelFileDescriptor, long, long, @NonNull String[]);
+    method public void applyPayload(@NonNull android.content.res.AssetFileDescriptor, @NonNull String[]);
     method public boolean bind(android.os.UpdateEngineCallback, android.os.Handler);
     method public boolean bind(android.os.UpdateEngineCallback);
     method public void cancel();
+    method public int cleanupAppliedPayload();
     method public void resetStatus();
     method public void resume();
     method public void suspend();
@@ -7073,14 +7285,21 @@
     method public boolean verifyPayloadMetadata(String);
   }
 
+  public static final class UpdateEngine.AllocateSpaceResult {
+    method public int errorCode();
+    method public long freeSpaceRequired();
+  }
+
   public static final class UpdateEngine.ErrorCodeConstants {
     ctor public UpdateEngine.ErrorCodeConstants();
+    field public static final int DEVICE_CORRUPTED = 61; // 0x3d
     field public static final int DOWNLOAD_PAYLOAD_VERIFICATION_ERROR = 12; // 0xc
     field public static final int DOWNLOAD_TRANSFER_ERROR = 9; // 0x9
     field public static final int ERROR = 1; // 0x1
     field public static final int FILESYSTEM_COPIER_ERROR = 4; // 0x4
     field public static final int INSTALL_DEVICE_OPEN_ERROR = 7; // 0x7
     field public static final int KERNEL_DEVICE_OPEN_ERROR = 8; // 0x8
+    field public static final int NOT_ENOUGH_SPACE = 60; // 0x3c
     field public static final int PAYLOAD_HASH_MISMATCH_ERROR = 10; // 0xa
     field public static final int PAYLOAD_MISMATCHED_TYPE_ERROR = 6; // 0x6
     field public static final int PAYLOAD_SIZE_MISMATCH_ERROR = 11; // 0xb
@@ -7139,7 +7358,6 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isAdminUser();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isGuestUser();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isPrimaryUser();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isRestrictedProfile();
@@ -7316,6 +7534,10 @@
     field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1
   }
 
+  public final class StorageVolume implements android.os.Parcelable {
+    method @NonNull public String getId();
+  }
+
 }
 
 package android.permission {
@@ -7725,6 +7947,7 @@
   }
 
   public final class Settings {
+    method public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, boolean);
     field public static final String ACTION_ACCESSIBILITY_DETAILS_SETTINGS = "android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
     field public static final String ACTION_BUGREPORT_HANDLER_SETTINGS = "android.settings.BUGREPORT_HANDLER_SETTINGS";
     field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
@@ -7735,6 +7958,7 @@
     field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS";
     field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE";
     field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
+    field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
   }
 
   public static final class Settings.Global extends android.provider.Settings.NameValueTable {
@@ -7754,6 +7978,7 @@
     field public static final String INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS = "install_carrier_app_notification_sleep_millis";
     field public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update";
     field public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt";
+    field public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled";
     field public static final String TETHER_SUPPORTED = "tether_supported";
     field public static final String THEATER_MODE_ON = "theater_mode_on";
     field public static final String WEBVIEW_MULTIPROCESS = "webview_multiprocess";
@@ -7886,14 +8111,77 @@
     field @NonNull public static final String ENABLE_CMAS_PRESIDENTIAL_PREF = "enable_cmas_presidential_alerts";
     field @NonNull public static final String ENABLE_CMAS_SEVERE_THREAT_PREF = "enable_cmas_severe_threat_alerts";
     field @NonNull public static final String ENABLE_EMERGENCY_PERF = "enable_emergency_alerts";
-    field @NonNull public static final String ENABLE_FULL_VOLUME_PREF = "use_full_volume";
     field @NonNull public static final String ENABLE_PUBLIC_SAFETY_PREF = "enable_public_safety_messages";
     field @NonNull public static final String ENABLE_STATE_LOCAL_TEST_PREF = "enable_state_local_test_alerts";
     field @NonNull public static final String ENABLE_TEST_ALERT_PREF = "enable_test_alerts";
   }
 
   public static final class Telephony.SimInfo {
+    field public static final String ACCESS_RULES = "access_rules";
+    field public static final String ACCESS_RULES_FROM_CARRIER_CONFIGS = "access_rules_from_carrier_configs";
+    field public static final String CARD_ID = "card_id";
+    field public static final String CARRIER_ID = "carrier_id";
+    field public static final String CARRIER_NAME = "carrier_name";
+    field public static final String CB_ALERT_REMINDER_INTERVAL = "alert_reminder_interval";
+    field public static final String CB_ALERT_SOUND_DURATION = "alert_sound_duration";
+    field public static final String CB_ALERT_SPEECH = "enable_alert_speech";
+    field public static final String CB_ALERT_VIBRATE = "enable_alert_vibrate";
+    field public static final String CB_AMBER_ALERT = "enable_cmas_amber_alerts";
+    field public static final String CB_CHANNEL_50_ALERT = "enable_channel_50_alerts";
+    field public static final String CB_CMAS_TEST_ALERT = "enable_cmas_test_alerts";
+    field public static final String CB_EMERGENCY_ALERT = "enable_emergency_alerts";
+    field public static final String CB_ETWS_TEST_ALERT = "enable_etws_test_alerts";
+    field public static final String CB_EXTREME_THREAT_ALERT = "enable_cmas_extreme_threat_alerts";
+    field public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog";
+    field public static final String CB_SEVERE_THREAT_ALERT = "enable_cmas_severe_threat_alerts";
+    field public static final String COLOR = "color";
     field @NonNull public static final android.net.Uri CONTENT_URI;
+    field public static final String DATA_ENABLED_OVERRIDE_RULES = "data_enabled_override_rules";
+    field public static final String DATA_ROAMING = "data_roaming";
+    field public static final int DATA_ROAMING_DEFAULT = 0; // 0x0
+    field public static final int DATA_ROAMING_DISABLE = 0; // 0x0
+    field public static final int DATA_ROAMING_ENABLE = 1; // 0x1
+    field public static final String DISPLAY_NAME = "display_name";
+    field public static final String EHPLMNS = "ehplmns";
+    field public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
+    field public static final String GROUP_OWNER = "group_owner";
+    field public static final String GROUP_UUID = "group_uuid";
+    field public static final String HPLMNS = "hplmns";
+    field public static final String ICC_ID = "icc_id";
+    field public static final String IMSI = "imsi";
+    field public static final String ISO_COUNTRY_CODE = "iso_country_code";
+    field public static final String IS_EMBEDDED = "is_embedded";
+    field public static final String IS_OPPORTUNISTIC = "is_opportunistic";
+    field public static final String IS_REMOVABLE = "is_removable";
+    field public static final String MCC = "mcc";
+    field public static final String MCC_STRING = "mcc_string";
+    field public static final String MNC = "mnc";
+    field public static final String MNC_STRING = "mnc_string";
+    field public static final String NAME_SOURCE = "name_source";
+    field public static final int NAME_SOURCE_CARRIER = 3; // 0x3
+    field public static final int NAME_SOURCE_DEFAULT = 0; // 0x0
+    field public static final int NAME_SOURCE_SIM_PNN = 4; // 0x4
+    field public static final int NAME_SOURCE_SIM_SPN = 1; // 0x1
+    field public static final int NAME_SOURCE_USER_INPUT = 2; // 0x2
+    field public static final String NUMBER = "number";
+    field public static final String PROFILE_CLASS = "profile_class";
+    field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff
+    field public static final int PROFILE_CLASS_OPERATIONAL = 2; // 0x2
+    field public static final int PROFILE_CLASS_PROVISIONING = 1; // 0x1
+    field public static final int PROFILE_CLASS_TESTING = 0; // 0x0
+    field public static final int PROFILE_CLASS_UNSET = -1; // 0xffffffff
+    field public static final int SIM_NOT_INSERTED = -1; // 0xffffffff
+    field public static final String SIM_SLOT_INDEX = "sim_id";
+    field public static final String SUBSCRIPTION_TYPE = "subscription_type";
+    field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0
+    field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1
+    field public static final String UICC_APPLICATIONS_ENABLED = "uicc_applications_enabled";
+    field public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id";
+    field public static final String VT_IMS_ENABLED = "vt_ims_enabled";
+    field public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
+    field public static final String WFC_IMS_MODE = "wfc_ims_mode";
+    field public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled";
+    field public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode";
   }
 
   public static final class Telephony.Sms.Intents {
@@ -8457,7 +8745,10 @@
     method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel);
     method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
     method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String);
+    method public void onNotificationVisibilityChanged(@NonNull String, boolean);
     method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>);
+    method public void onPanelHidden();
+    method public void onPanelRevealed(int);
     method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
     method public final void unsnoozeNotification(@NonNull String);
     field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
@@ -9160,30 +9451,37 @@
 
   public abstract class CellIdentity implements android.os.Parcelable {
     method @NonNull public abstract android.telephony.CellLocation asCellLocation();
+    method @NonNull public abstract android.telephony.CellIdentity sanitizeLocationInfo();
   }
 
   public final class CellIdentityCdma extends android.telephony.CellIdentity {
     method @NonNull public android.telephony.cdma.CdmaCellLocation asCellLocation();
+    method @NonNull public android.telephony.CellIdentityCdma sanitizeLocationInfo();
   }
 
   public final class CellIdentityGsm extends android.telephony.CellIdentity {
     method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation();
+    method @NonNull public android.telephony.CellIdentityGsm sanitizeLocationInfo();
   }
 
   public final class CellIdentityLte extends android.telephony.CellIdentity {
     method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation();
+    method @NonNull public android.telephony.CellIdentityLte sanitizeLocationInfo();
   }
 
   public final class CellIdentityNr extends android.telephony.CellIdentity {
     method @NonNull public android.telephony.CellLocation asCellLocation();
+    method @NonNull public android.telephony.CellIdentityNr sanitizeLocationInfo();
   }
 
   public final class CellIdentityTdscdma extends android.telephony.CellIdentity {
     method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation();
+    method @NonNull public android.telephony.CellIdentityTdscdma sanitizeLocationInfo();
   }
 
   public final class CellIdentityWcdma extends android.telephony.CellIdentity {
     method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation();
+    method @NonNull public android.telephony.CellIdentityWcdma sanitizeLocationInfo();
   }
 
   public final class DataFailCause {
@@ -10171,6 +10469,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode();
     method public int getEmergencyNumberDbVersion();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getIsimImpu();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
     method public int getMaxNumberOfSimultaneouslyActiveSims();
@@ -10191,14 +10490,15 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoiceActivationState();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmi(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmiForSubscriber(int, String);
-    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean iccCloseLogicalChannelBySlot(int, int);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean iccCloseLogicalChannelBySlot(int, int);
     method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(int, @Nullable String, int);
-    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String);
-    method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String);
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String);
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAnyRadioPoweredOn();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int);
     method public boolean isCurrentSimOperator(@NonNull String, int, @Nullable String);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataConnectionEnabled();
     method public boolean isDataConnectivityPossible();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
@@ -10259,7 +10559,6 @@
     field public static final String ACTION_EMERGENCY_ASSISTANCE = "android.telephony.action.EMERGENCY_ASSISTANCE";
     field public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED = "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED";
     field public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED = "android.intent.action.EMERGENCY_CALL_STATE_CHANGED";
-    field public static final String ACTION_NETWORK_SET_TIME = "android.telephony.action.NETWORK_SET_TIME";
     field public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE = "com.android.omadm.service.CONFIGURATION_UPDATE";
     field public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS = "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS";
     field public static final String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED";
@@ -10361,6 +10660,20 @@
     method public static final void setSmsFilterSettings(android.content.Context, android.telecom.PhoneAccountHandle, android.telephony.VisualVoicemailSmsFilterSettings);
   }
 
+  public final class WapPushManagerConnector {
+    ctor public WapPushManagerConnector(@NonNull android.content.Context);
+    method public boolean bindToWapPushManagerService();
+    method @Nullable public String getConnectedWapPushManagerServicePackage();
+    method public int processMessage(@NonNull String, @NonNull String, @NonNull android.content.Intent);
+    method public void unbindWapPushManagerService();
+    field public static final int RESULT_APP_QUERY_FAILED = 2; // 0x2
+    field public static final int RESULT_EXCEPTION_CAUGHT = 16; // 0x10
+    field public static final int RESULT_FURTHER_PROCESSING = 32768; // 0x8000
+    field public static final int RESULT_INVALID_RECEIVER_NAME = 8; // 0x8
+    field public static final int RESULT_MESSAGE_HANDLED = 1; // 0x1
+    field public static final int RESULT_SIGNATURE_NO_MATCH = 4; // 0x4
+  }
+
 }
 
 package android.telephony.cdma {
@@ -10861,6 +11174,7 @@
   public class ImsManager {
     method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int);
     method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int);
+    field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
   }
 
   public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
@@ -11160,8 +11474,22 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+    field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19
+    field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13
+    field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12
+    field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14
+    field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11
+    field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17
+    field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16
+    field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15
+    field public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; // 0x10
+    field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf
+    field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7
     field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
     field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
+    field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa
+    field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb
+    field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff
     field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
     field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
     field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
@@ -11174,6 +11502,47 @@
     method public void onProvisioningStringChanged(int, @NonNull String);
   }
 
+  public final class RcsContactUceCapability implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<java.lang.String> getCapableExtensionTags();
+    method @NonNull public android.net.Uri getContactUri();
+    method @Nullable public android.net.Uri getServiceUri(int);
+    method public boolean isCapable(int);
+    method public boolean isCapable(@NonNull String);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CAPABILITY_CHAT_SESSION = 2; // 0x2
+    field public static final int CAPABILITY_CHAT_SESSION_STORE_FORWARD = 4; // 0x4
+    field public static final int CAPABILITY_CHAT_STANDALONE = 1; // 0x1
+    field public static final int CAPABILITY_DISCOVERY_VIA_PRESENCE = 4096; // 0x1000
+    field public static final int CAPABILITY_FILE_TRANSFER = 8; // 0x8
+    field public static final int CAPABILITY_FILE_TRANSFER_HTTP = 64; // 0x40
+    field public static final int CAPABILITY_FILE_TRANSFER_SMS = 128; // 0x80
+    field public static final int CAPABILITY_FILE_TRANSFER_STORE_FORWARD = 32; // 0x20
+    field public static final int CAPABILITY_FILE_TRANSFER_THUMBNAIL = 16; // 0x10
+    field public static final int CAPABILITY_GEOLOCATION_PULL = 131072; // 0x20000
+    field public static final int CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER = 262144; // 0x40000
+    field public static final int CAPABILITY_GEOLOCATION_PUSH = 32768; // 0x8000
+    field public static final int CAPABILITY_GEOLOCATION_PUSH_SMS = 65536; // 0x10000
+    field public static final int CAPABILITY_IMAGE_SHARE = 256; // 0x100
+    field public static final int CAPABILITY_IP_VIDEO_CALL = 16384; // 0x4000
+    field public static final int CAPABILITY_IP_VOICE_CALL = 8192; // 0x2000
+    field public static final int CAPABILITY_RCS_VIDEO_CALL = 1048576; // 0x100000
+    field public static final int CAPABILITY_RCS_VIDEO_ONLY_CALL = 2097152; // 0x200000
+    field public static final int CAPABILITY_RCS_VOICE_CALL = 524288; // 0x80000
+    field public static final int CAPABILITY_SOCIAL_PRESENCE = 2048; // 0x800
+    field public static final int CAPABILITY_VIDEO_SHARE = 1024; // 0x400
+    field public static final int CAPABILITY_VIDEO_SHARE_DURING_CS_CALL = 512; // 0x200
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsContactUceCapability> CREATOR;
+  }
+
+  public static class RcsContactUceCapability.Builder {
+    ctor public RcsContactUceCapability.Builder(@NonNull android.net.Uri);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(int, @NonNull android.net.Uri);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(int);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(@NonNull String);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability build();
+  }
+
   public interface RegistrationManager {
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
@@ -11611,6 +11980,8 @@
   public final class AccessibilityManager {
     method public int getAccessibilityWindowId(@Nullable android.os.IBinder);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void performAccessibilityShortcut();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void registerSystemAction(@NonNull android.app.RemoteAction, int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void unregisterSystemAction(int);
   }
 
 }
diff --git a/api/test-current.txt b/api/test-current.txt
index 9967942..6cc070a 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -34,6 +34,7 @@
   public static final class R.string {
     field public static final int config_defaultAssistant = 17039393; // 0x1040021
     field public static final int config_defaultDialer = 17039395; // 0x1040023
+    field public static final int config_systemGallery = 17039402; // 0x104002a
   }
 
 }
@@ -435,8 +436,11 @@
   }
 
   public class StatusBarManager {
+    method public void collapsePanels();
+    method public void expandNotificationsPanel();
     method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo();
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean);
+    method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean);
   }
 
   public static final class StatusBarManager.DisableInfo {
@@ -2551,6 +2555,7 @@
     field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
     field public static final String ACTION_MANAGE_APP_OVERLAY_PERMISSION = "android.settings.MANAGE_APP_OVERLAY_PERMISSION";
     field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE";
+    field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
     field public static final int RESET_MODE_PACKAGE_DEFAULTS = 1; // 0x1
   }
 
@@ -2569,6 +2574,7 @@
     field public static final String LOW_POWER_MODE_STICKY = "low_power_sticky";
     field public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
     field public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices";
+    field public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled";
     field public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
   }
 
@@ -2926,7 +2932,10 @@
     method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel);
     method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
     method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String);
+    method public void onNotificationVisibilityChanged(@NonNull String, boolean);
     method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>);
+    method public void onPanelHidden();
+    method public void onPanelRevealed(int);
     method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
     method public final void unsnoozeNotification(@NonNull String);
     field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
@@ -3514,6 +3523,7 @@
   public class ImsManager {
     method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int);
     method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int);
+    field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
   }
 
   public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
@@ -3809,8 +3819,22 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+    field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19
+    field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13
+    field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12
+    field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14
+    field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11
+    field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17
+    field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16
+    field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15
+    field public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; // 0x10
+    field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf
+    field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7
     field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
     field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
+    field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa
+    field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb
+    field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff
     field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
     field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
     field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index 229628c..4074789 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -33,6 +33,8 @@
 
 namespace {
 
+#define REWRITE_PACKAGE(resid, package_id) \
+  (((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U))
 #define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24)
 
 std::string ConcatPolicies(const std::vector<std::string>& policies) {
@@ -154,6 +156,7 @@
     return Error("root element is not <overlay> tag");
   }
 
+  const uint8_t target_package_id = target_package->GetPackageId();
   const uint8_t overlay_package_id = overlay_package->GetPackageId();
   auto overlay_it_end = root_it.end();
   for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) {
@@ -187,6 +190,9 @@
       continue;
     }
 
+    // Retrieve the compile-time resource id of the target resource.
+    target_id = REWRITE_PACKAGE(target_id, target_package_id);
+
     if (overlay_resource->dataType == Res_value::TYPE_STRING) {
       overlay_resource->data += string_pool_offset;
     }
@@ -214,6 +220,7 @@
     const AssetManager2* target_am, const AssetManager2* overlay_am,
     const LoadedPackage* target_package, const LoadedPackage* overlay_package) {
   ResourceMapping resource_mapping;
+  const uint8_t target_package_id = target_package->GetPackageId();
   const auto end = overlay_package->end();
   for (auto iter = overlay_package->begin(); iter != end; ++iter) {
     const ResourceId overlay_resid = *iter;
@@ -225,11 +232,14 @@
     // Find the resource with the same type and entry name within the target package.
     const std::string full_name =
         base::StringPrintf("%s:%s", target_package->GetPackageName().c_str(), name->c_str());
-    const ResourceId target_resource = target_am->GetResourceId(full_name);
+    ResourceId target_resource = target_am->GetResourceId(full_name);
     if (target_resource == 0U) {
       continue;
     }
 
+    // Retrieve the compile-time resource id of the target resource.
+    target_resource = REWRITE_PACKAGE(target_resource, target_package_id);
+
     resource_mapping.AddMapping(target_resource, Res_value::TYPE_REFERENCE, overlay_resid,
                                 /* rewrite_overlay_reference */ false);
   }
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 7b96ce9..1c6867c3 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -78,7 +78,6 @@
         "src/external/StatsPuller.cpp",
         "src/external/StatsPullerManager.cpp",
         "src/external/SubsystemSleepStatePuller.cpp",
-        "src/external/SurfaceflingerStatsPuller.cpp",
         "src/external/TrainInfoPuller.cpp",
         "src/FieldValue.cpp",
         "src/guardrail/StatsdStats.cpp",
@@ -126,30 +125,28 @@
     ],
 
     static_libs: [
-        "libhealthhalutils",
-        "libplatformprotos",
-    ],
-
-    shared_libs: [
         "android.frameworks.stats@1.0",
-        "android.hardware.health@2.0",
         "android.hardware.power.stats@1.0",
         "android.hardware.power@1.0",
         "android.hardware.power@1.1",
         "libbase",
-        "libbinder",
         "libcutils",
+        "libhealthhalutils",
+        "liblog",
+        "libplatformprotos",
+        "libprotoutil",
+        "libstatslog",
+        "libstatssocket",
+        "libsysutils",
+    ],
+    shared_libs: [
+        "android.hardware.health@2.0",
+        "libbinder",
         "libgraphicsenv",
         "libhidlbase",
         "libincident",
-        "liblog",
-        "libprotoutil",
         "libservices",
-        "libstatslog",
         "libstatsmetadata",
-        "libstatssocket",
-        "libsysutils",
-        "libtimestats_proto",
         "libutils",
     ],
 }
@@ -298,7 +295,6 @@
         "tests/external/puller_util_test.cpp",
         "tests/external/StatsCallbackPuller_test.cpp",
         "tests/external/StatsPuller_test.cpp",
-        "tests/external/SurfaceflingerStatsPuller_test.cpp",
         "tests/FieldValue_test.cpp",
         "tests/guardrail/StatsdStats_test.cpp",
         "tests/indexed_priority_queue_test.cpp",
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 5ff0f97..1ca19c3 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -1135,12 +1135,11 @@
     }
 }
 
-Status StatsService::getData(int64_t key, const String16& packageName, vector<uint8_t>* output) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+Status StatsService::getData(int64_t key, const int32_t callingUid, vector<uint8_t>* output) {
+    ENFORCE_UID(AID_SYSTEM);
 
-    IPCThreadState* ipc = IPCThreadState::self();
-    VLOG("StatsService::getData with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid());
-    ConfigKey configKey(ipc->getCallingUid(), key);
+    VLOG("StatsService::getData with Uid %i", callingUid);
+    ConfigKey configKey(callingUid, key);
     // The dump latency does not matter here since we do not include the current bucket, we do not
     // need to pull any new data anyhow.
     mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(), false /* include_current_bucket*/,
@@ -1148,22 +1147,18 @@
     return Status::ok();
 }
 
-Status StatsService::getMetadata(const String16& packageName, vector<uint8_t>* output) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+Status StatsService::getMetadata(vector<uint8_t>* output) {
+    ENFORCE_UID(AID_SYSTEM);
 
-    IPCThreadState* ipc = IPCThreadState::self();
-    VLOG("StatsService::getMetadata with Pid %i, Uid %i", ipc->getCallingPid(),
-         ipc->getCallingUid());
     StatsdStats::getInstance().dumpStats(output, false); // Don't reset the counters.
     return Status::ok();
 }
 
 Status StatsService::addConfiguration(int64_t key, const vector <uint8_t>& config,
-                                      const String16& packageName) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+                                      const int32_t callingUid) {
+    ENFORCE_UID(AID_SYSTEM);
 
-    IPCThreadState* ipc = IPCThreadState::self();
-    if (addConfigurationChecked(ipc->getCallingUid(), key, config)) {
+    if (addConfigurationChecked(callingUid, key, config)) {
         return Status::ok();
     } else {
         ALOGE("Could not parse malformatted StatsdConfig");
@@ -1228,13 +1223,11 @@
     return Status::ok();
 }
 
-Status StatsService::removeConfiguration(int64_t key, const String16& packageName) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+Status StatsService::removeConfiguration(int64_t key, const int32_t callingUid) {
+    ENFORCE_UID(AID_SYSTEM);
 
-    IPCThreadState* ipc = IPCThreadState::self();
-    ConfigKey configKey(ipc->getCallingUid(), key);
+    ConfigKey configKey(callingUid, key);
     mConfigManager->RemoveConfig(configKey);
-    SubscriberReporter::getInstance().removeConfig(configKey);
     return Status::ok();
 }
 
@@ -1477,17 +1470,7 @@
 
 
 Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experimentIdsOut) {
-    uid_t uid = IPCThreadState::self()->getCallingUid();
-
-    // Caller must be granted these permissions
-    if (!checkCallingPermission(String16(kPermissionDump))) {
-        return exception(binder::Status::EX_SECURITY,
-                         StringPrintf("UID %d lacks permission %s", uid, kPermissionDump));
-    }
-    if (!checkCallingPermission(String16(kPermissionUsage))) {
-        return exception(binder::Status::EX_SECURITY,
-                         StringPrintf("UID %d lacks permission %s", uid, kPermissionUsage));
-    }
+    ENFORCE_UID(AID_SYSTEM);
     // TODO: add verifier permission
 
     // Read the latest train info
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 0565f3c..c9a9072 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -99,15 +99,14 @@
      * Binder call for clients to request data for this configuration key.
      */
     virtual Status getData(int64_t key,
-                           const String16& packageName,
+                           const int32_t callingUid,
                            vector<uint8_t>* output) override;
 
 
     /**
      * Binder call for clients to get metadata across all configs in statsd.
      */
-    virtual Status getMetadata(const String16& packageName,
-                               vector<uint8_t>* output) override;
+    virtual Status getMetadata(vector<uint8_t>* output) override;
 
 
     /**
@@ -116,7 +115,7 @@
      */
     virtual Status addConfiguration(int64_t key,
                                     const vector<uint8_t>& config,
-                                    const String16& packageName) override;
+                                    const int32_t callingUid) override;
 
     /**
      * Binder call to let clients register the data fetch operation for a configuration.
@@ -146,7 +145,7 @@
      * Binder call to allow clients to remove the specified configuration.
      */
     virtual Status removeConfiguration(int64_t key,
-                                       const String16& packageName) override;
+                                       const int32_t callingUid) override;
 
     /**
      * Binder call to associate the given config's subscriberId with the given pendingIntentRef.
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 19b9709..fb43783 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -333,10 +333,11 @@
         MediaProviderSchemaChange media_provider_schema_change = 236 [(module) = "mediaprovider"];
         MediaProviderIdleMaintenance media_provider_idle_maintenance =
             237 [(module) = "mediaprovider"];
+        RebootEscrowRecoveryReported reboot_escrow_recovery_reported = 238;
     }
 
     // Pulled events will start at field 10000.
-    // Next: 10068
+    // Next: 10069
     oneof pulled {
         WifiBytesTransfer wifi_bytes_transfer = 10000;
         WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -405,6 +406,7 @@
         VmsClientStats vms_client_stats = 10065;
         NotificationRemoteViews notification_remote_views = 10066;
         DangerousPermissionStateSampled dangerous_permission_state_sampled = 10067;
+        GraphicsStats graphics_stats = 10068;
     }
 
     // DO NOT USE field numbers above 100,000 in AOSP.
@@ -4723,36 +4725,69 @@
 }
 
 message AggStats {
-    optional int64 min = 1;
+    // These are all in byte resolution.
+    optional int64 min = 1 [deprecated = true];
+    optional int64 average = 2 [deprecated = true];
+    optional int64 max = 3 [deprecated = true];
 
-    optional int64 average = 2;
-
-    optional int64 max = 3;
+    // These are all in kilobyte resolution. Can fit in int32, so smaller on the wire than the above
+    // int64 fields.
+    optional int32 mean_kb = 4;
+    optional int32 max_kb = 5;
 }
 
+// A reduced subset of process states; reducing the number of possible states allows more
+// aggressive device-side aggregation of statistics and hence reduces metric upload size.
+enum ProcessStateAggregated {
+    PROCESS_STATE_UNKNOWN = 0;
+    // Persistent system process.
+    PROCESS_STATE_PERSISTENT = 1;
+    // Top activity; actually any visible activity.
+    PROCESS_STATE_TOP = 2;
+    // Process binding to top or a foreground service.
+    PROCESS_STATE_BOUND_TOP_OR_FGS = 3;
+    // Processing running a foreground service.
+    PROCESS_STATE_FGS = 4;
+    // Important foreground process (ime, wallpaper, etc).
+    PROCESS_STATE_IMPORTANT_FOREGROUND = 5;
+    // Important background process.
+    PROCESS_STATE_BACKGROUND = 6;
+    // Process running a receiver.
+    PROCESS_STATE_RECEIVER = 7;
+    // All kinds of cached processes.
+    PROCESS_STATE_CACHED = 8;
+}
+
+// Next tag: 13
 message ProcessStatsStateProto {
     optional android.service.procstats.ScreenState screen_state = 1;
 
-    optional android.service.procstats.MemoryState memory_state = 2;
+    optional android.service.procstats.MemoryState memory_state = 2 [deprecated = true];
 
     // this enum list is from frameworks/base/core/java/com/android/internal/app/procstats/ProcessStats.java
     // and not frameworks/base/core/java/android/app/ActivityManager.java
-    optional android.service.procstats.ProcessState process_state = 3;
+    optional android.service.procstats.ProcessState process_state = 3 [deprecated = true];
+
+    optional ProcessStateAggregated process_state_aggregated = 10;
 
     // Millisecond uptime duration spent in this state
-    optional int64 duration_millis = 4;
+    optional int64 duration_millis = 4 [deprecated = true];
+    // Same as above, but with minute resolution so it fits into an int32.
+    optional int32 duration_minutes = 11;
 
     // Millisecond elapsed realtime duration spent in this state
-    optional int64 realtime_duration_millis = 9;
+    optional int64 realtime_duration_millis = 9 [deprecated = true];
+    // Same as above, but with minute resolution so it fits into an int32.
+    optional int32 realtime_duration_minutes = 12;
 
     // # of samples taken
     optional int32 sample_size = 5;
 
     // PSS is memory reserved for this process
-    optional AggStats pss = 6;
+    optional AggStats pss = 6 [deprecated = true];
 
     // USS is memory shared between processes, divided evenly for accounting
-    optional AggStats uss = 7;
+    optional AggStats uss = 7 [deprecated = true];
 
     // RSS is memory resident for this process
     optional AggStats rss = 8;
@@ -4777,7 +4812,7 @@
         // PSS stats during cached kill
         optional AggStats cached_pss = 3;
     }
-    optional Kill kill = 3;
+    optional Kill kill = 3 [deprecated = true];
 
     // Time and memory spent in various states.
     repeated ProcessStatsStateProto states = 5;
@@ -7338,6 +7373,17 @@
 }
 
 /**
+ * Reported when the RebootEscrow HAL has attempted to recover the escrowed
+ * key to indicate whether it was successful or not.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+ */
+message RebootEscrowRecoveryReported {
+    optional bool successful = 1;
+}
+
+/**
  * Global display pipeline metrics reported by SurfaceFlinger.
  * Pulled from:
  *    frameworks/native/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -7553,3 +7599,69 @@
     optional int32 permission_flags = 4;
 }
 
+/**
+ * HWUI renders pipeline type: GL (0) or Vulkan (1).
+ */
+enum PipelineType {
+    GL = 0;
+    VULKAN = 1;
+}
+
+/**
+ * HWUI stats for a given app.
+ */
+message GraphicsStats {
+    // The package name of the app
+    optional string package_name = 1;
+
+    // The version code of the app
+    optional int64 version_code = 2;
+
+    // The start & end timestamps in UTC as
+    // milliseconds since January 1, 1970
+    // Compatible with java.util.Date#setTime()
+    optional int64 stats_start = 3;
+
+    optional int64 stats_end = 4;
+
+    // HWUI renders pipeline type: GL or Vulkan.
+    optional PipelineType pipeline = 5;
+
+    // Distinct frame count.
+    optional int32 total_frames = 6;
+
+    // Number of "missed vsync" events.
+    optional int32 missed_vsync_count = 7;
+
+    // Number of frames in triple-buffering scenario (high input latency)
+    optional int32 high_input_latency_count = 8;
+
+    // Number of "slow UI thread" events.
+    optional int32 slow_ui_thread_count = 9;
+
+    // Number of "slow bitmap upload" events.
+    optional int32 slow_bitmap_upload_count = 10;
+
+    // Number of "slow draw" events.
+    optional int32 slow_draw_count = 11;
+
+    // Number of frames that missed their deadline (aka, visibly janked)
+    optional int32 missed_deadline_count = 12;
+
+    // The frame time histogram for the package
+    optional FrameTimingHistogram cpu_histogram = 13
+    [(android.os.statsd.log_mode) = MODE_BYTES];
+
+    // The gpu frame time histogram for the package
+    optional FrameTimingHistogram gpu_histogram = 14
+    [(android.os.statsd.log_mode) = MODE_BYTES];
+
+    // UI mainline module version.
+    optional int64 version_ui_module = 15;
+
+    // If true, these are HWUI stats for up to a 24h period for a given app from today.
+    // If false, these are HWUI stats for a 24h period for a given app from the last complete
+    // day (yesterday). Stats from yesterday stay constant, while stats from today may change as
+    // more apps are running / rendering.
+    optional bool is_today = 16;
+}
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 55d73c1..972adf7 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -189,25 +189,11 @@
             // Remove from map
             uidIt->second.erase(key);
 
-            // No more configs for this uid, lets remove the active configs callback.
-            if (uidIt->second.empty()) {
-                auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid);
-                    if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) {
-                        mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver);
-                    }
-            }
-
             for (const sp<ConfigListener>& listener : mListeners) {
                 broadcastList.push_back(listener);
             }
         }
 
-        auto itReceiver = mConfigReceivers.find(key);
-        if (itReceiver != mConfigReceivers.end()) {
-            // Remove from map
-            mConfigReceivers.erase(itReceiver);
-        }
-
         // Remove from disk. There can still be a lingering file on disk so we check
         // whether or not the config was on memory.
         remove_saved_configs(key);
@@ -238,12 +224,6 @@
             // Remove from map
                 remove_saved_configs(*it);
                 removed.push_back(*it);
-                mConfigReceivers.erase(*it);
-        }
-
-        auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid);
-        if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) {
-            mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver);
         }
 
         mConfigs.erase(uidIt);
@@ -277,8 +257,6 @@
             uidIt = mConfigs.erase(uidIt);
         }
 
-        mConfigReceivers.clear();
-        mActiveConfigsChangedReceivers.clear();
         for (const sp<ConfigListener>& listener : mListeners) {
             broadcastList.push_back(listener);
         }
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 50896f8..b568033 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -41,7 +41,6 @@
 #include "StatsCallbackPullerDeprecated.h"
 #include "StatsCompanionServicePuller.h"
 #include "SubsystemSleepStatePuller.h"
-#include "SurfaceflingerStatsPuller.h"
 #include "TrainInfoPuller.h"
 #include "statslog.h"
 
@@ -60,161 +59,200 @@
 const int64_t NO_ALARM_UPDATE = INT64_MAX;
 
 std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
-        // wifi_bytes_transfer
-        {{.atomTag = android::util::WIFI_BYTES_TRANSFER},
-         {.additiveFields = {2, 3, 4, 5},
-          .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER)}},
+
         // wifi_bytes_transfer_by_fg_bg
         {{.atomTag = android::util::WIFI_BYTES_TRANSFER_BY_FG_BG},
          {.additiveFields = {3, 4, 5, 6},
           .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER_BY_FG_BG)}},
+
         // mobile_bytes_transfer
         {{.atomTag = android::util::MOBILE_BYTES_TRANSFER},
          {.additiveFields = {2, 3, 4, 5},
           .puller = new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER)}},
+
         // mobile_bytes_transfer_by_fg_bg
         {{.atomTag = android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG},
          {.additiveFields = {3, 4, 5, 6},
           .puller =
                   new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)}},
+
         // bluetooth_bytes_transfer
         {{.atomTag = android::util::BLUETOOTH_BYTES_TRANSFER},
          {.additiveFields = {2, 3},
           .puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_BYTES_TRANSFER)}},
+
         // kernel_wakelock
         {{.atomTag = android::util::KERNEL_WAKELOCK},
          {.puller = new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}},
+
         // subsystem_sleep_state
         {{.atomTag = android::util::SUBSYSTEM_SLEEP_STATE},
          {.puller = new SubsystemSleepStatePuller()}},
+
         // on_device_power_measurement
         {{.atomTag = android::util::ON_DEVICE_POWER_MEASUREMENT},
          {.puller = new PowerStatsPuller()}},
+
         // cpu_time_per_freq
         {{.atomTag = android::util::CPU_TIME_PER_FREQ},
          {.additiveFields = {3},
           .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_FREQ)}},
+
         // cpu_time_per_uid
         {{.atomTag = android::util::CPU_TIME_PER_UID},
          {.additiveFields = {2, 3},
           .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID)}},
+
         // cpu_time_per_uid_freq
         // the throttling is 3sec, handled in
         // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
         {{.atomTag = android::util::CPU_TIME_PER_UID_FREQ},
          {.additiveFields = {4},
           .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID_FREQ)}},
+
         // cpu_active_time
         // the throttling is 3sec, handled in
         // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
         {{.atomTag = android::util::CPU_ACTIVE_TIME},
          {.additiveFields = {2},
           .puller = new StatsCompanionServicePuller(android::util::CPU_ACTIVE_TIME)}},
+
         // cpu_cluster_time
         // the throttling is 3sec, handled in
         // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
         {{.atomTag = android::util::CPU_CLUSTER_TIME},
          {.additiveFields = {3},
           .puller = new StatsCompanionServicePuller(android::util::CPU_CLUSTER_TIME)}},
+
         // wifi_activity_energy_info
         {{.atomTag = android::util::WIFI_ACTIVITY_INFO},
          {.puller = new StatsCompanionServicePuller(android::util::WIFI_ACTIVITY_INFO)}},
+
         // modem_activity_info
         {{.atomTag = android::util::MODEM_ACTIVITY_INFO},
          {.puller = new StatsCompanionServicePuller(android::util::MODEM_ACTIVITY_INFO)}},
+
         // bluetooth_activity_info
         {{.atomTag = android::util::BLUETOOTH_ACTIVITY_INFO},
          {.puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}},
+
         // system_elapsed_realtime
         {{.atomTag = android::util::SYSTEM_ELAPSED_REALTIME},
          {.coolDownNs = NS_PER_SEC,
           .puller = new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME),
           .pullTimeoutNs = NS_PER_SEC / 2,
          }},
+
         // system_uptime
         {{.atomTag = android::util::SYSTEM_UPTIME},
          {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}},
+
         // remaining_battery_capacity
         {{.atomTag = android::util::REMAINING_BATTERY_CAPACITY},
          {.puller = new ResourceHealthManagerPuller(android::util::REMAINING_BATTERY_CAPACITY)}},
+
         // full_battery_capacity
         {{.atomTag = android::util::FULL_BATTERY_CAPACITY},
          {.puller = new ResourceHealthManagerPuller(android::util::FULL_BATTERY_CAPACITY)}},
+
         // battery_voltage
         {{.atomTag = android::util::BATTERY_VOLTAGE},
          {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}},
+
         // battery_level
         {{.atomTag = android::util::BATTERY_LEVEL},
          {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_LEVEL)}},
+
         // battery_cycle_count
         {{.atomTag = android::util::BATTERY_CYCLE_COUNT},
          {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_CYCLE_COUNT)}},
+
         // process_memory_state
         {{.atomTag = android::util::PROCESS_MEMORY_STATE},
          {.additiveFields = {4, 5, 6, 7, 8},
           .puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_STATE)}},
+
         // process_memory_high_water_mark
         {{.atomTag = android::util::PROCESS_MEMORY_HIGH_WATER_MARK},
          {.puller =
                   new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}},
+
         // process_memory_snapshot
         {{.atomTag = android::util::PROCESS_MEMORY_SNAPSHOT},
          {.puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_SNAPSHOT)}},
+
         // system_ion_heap_size
         {{.atomTag = android::util::SYSTEM_ION_HEAP_SIZE},
          {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_ION_HEAP_SIZE)}},
+
         // process_system_ion_heap_size
         {{.atomTag = android::util::PROCESS_SYSTEM_ION_HEAP_SIZE},
          {.puller = new StatsCompanionServicePuller(android::util::PROCESS_SYSTEM_ION_HEAP_SIZE)}},
+
         // temperature
         {{.atomTag = android::util::TEMPERATURE},
          {.puller = new StatsCompanionServicePuller(android::util::TEMPERATURE)}},
+
         // cooling_device
         {{.atomTag = android::util::COOLING_DEVICE},
          {.puller = new StatsCompanionServicePuller(android::util::COOLING_DEVICE)}},
+
         // binder_calls
         {{.atomTag = android::util::BINDER_CALLS},
          {.additiveFields = {4, 5, 6, 8, 12},
           .puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS)}},
+
         // binder_calls_exceptions
         {{.atomTag = android::util::BINDER_CALLS_EXCEPTIONS},
          {.puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS_EXCEPTIONS)}},
+
         // looper_stats
         {{.atomTag = android::util::LOOPER_STATS},
          {.additiveFields = {5, 6, 7, 8, 9},
           .puller = new StatsCompanionServicePuller(android::util::LOOPER_STATS)}},
+
         // Disk Stats
         {{.atomTag = android::util::DISK_STATS},
          {.puller = new StatsCompanionServicePuller(android::util::DISK_STATS)}},
+
         // Directory usage
         {{.atomTag = android::util::DIRECTORY_USAGE},
          {.puller = new StatsCompanionServicePuller(android::util::DIRECTORY_USAGE)}},
+
         // Size of app's code, data, and cache
         {{.atomTag = android::util::APP_SIZE},
          {.puller = new StatsCompanionServicePuller(android::util::APP_SIZE)}},
+
         // Size of specific categories of files. Eg. Music.
         {{.atomTag = android::util::CATEGORY_SIZE},
          {.puller = new StatsCompanionServicePuller(android::util::CATEGORY_SIZE)}},
+
         // Number of fingerprints enrolled for each user.
         {{.atomTag = android::util::NUM_FINGERPRINTS_ENROLLED},
          {.puller = new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS_ENROLLED)}},
+
         // Number of faces enrolled for each user.
         {{.atomTag = android::util::NUM_FACES_ENROLLED},
          {.puller = new StatsCompanionServicePuller(android::util::NUM_FACES_ENROLLED)}},
+
         // ProcStats.
         {{.atomTag = android::util::PROC_STATS},
          {.puller = new StatsCompanionServicePuller(android::util::PROC_STATS)}},
+
         // ProcStatsPkgProc.
         {{.atomTag = android::util::PROC_STATS_PKG_PROC},
          {.puller = new StatsCompanionServicePuller(android::util::PROC_STATS_PKG_PROC)}},
+
         // Disk I/O stats per uid.
         {{.atomTag = android::util::DISK_IO},
          {.additiveFields = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
           .coolDownNs = 3 * NS_PER_SEC,
           .puller = new StatsCompanionServicePuller(android::util::DISK_IO)}},
+
         // PowerProfile constants for power model calculations.
         {{.atomTag = android::util::POWER_PROFILE},
          {.puller = new StatsCompanionServicePuller(android::util::POWER_PROFILE)}},
+
         // Process cpu stats. Min cool-down is 5 sec, inline with what AcitivityManagerService uses.
         {{.atomTag = android::util::PROCESS_CPU_TIME},
          {.coolDownNs = 5 * NS_PER_SEC /* min cool-down in seconds*/,
@@ -222,68 +260,83 @@
         {{.atomTag = android::util::CPU_TIME_PER_THREAD_FREQ},
          {.additiveFields = {7, 9, 11, 13, 15, 17, 19, 21},
           .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_THREAD_FREQ)}},
+
         // DeviceCalculatedPowerUse.
         {{.atomTag = android::util::DEVICE_CALCULATED_POWER_USE},
          {.puller = new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_USE)}},
+
         // DeviceCalculatedPowerBlameUid.
         {{.atomTag = android::util::DEVICE_CALCULATED_POWER_BLAME_UID},
          {.puller = new StatsCompanionServicePuller(
                   android::util::DEVICE_CALCULATED_POWER_BLAME_UID)}},
+
         // DeviceCalculatedPowerBlameOther.
         {{.atomTag = android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER},
          {.puller = new StatsCompanionServicePuller(
                   android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}},
+
         // DebugElapsedClock.
         {{.atomTag = android::util::DEBUG_ELAPSED_CLOCK},
          {.additiveFields = {1, 2, 3, 4},
           .puller = new StatsCompanionServicePuller(android::util::DEBUG_ELAPSED_CLOCK)}},
+
         // DebugFailingElapsedClock.
         {{.atomTag = android::util::DEBUG_FAILING_ELAPSED_CLOCK},
          {.additiveFields = {1, 2, 3, 4},
           .puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}},
+
         // BuildInformation.
         {{.atomTag = android::util::BUILD_INFORMATION},
          {.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}},
+
         // RoleHolder.
         {{.atomTag = android::util::ROLE_HOLDER},
          {.puller = new StatsCompanionServicePuller(android::util::ROLE_HOLDER)}},
+
         // PermissionState.
         {{.atomTag = android::util::DANGEROUS_PERMISSION_STATE},
          {.puller = new StatsCompanionServicePuller(android::util::DANGEROUS_PERMISSION_STATE)}},
+
         // TrainInfo.
         {{.atomTag = android::util::TRAIN_INFO}, {.puller = new TrainInfoPuller()}},
+
         // TimeZoneDataInfo.
         {{.atomTag = android::util::TIME_ZONE_DATA_INFO},
          {.puller = new StatsCompanionServicePuller(android::util::TIME_ZONE_DATA_INFO)}},
+
         // ExternalStorageInfo
         {{.atomTag = android::util::EXTERNAL_STORAGE_INFO},
          {.puller = new StatsCompanionServicePuller(android::util::EXTERNAL_STORAGE_INFO)}},
+
         // GpuStatsGlobalInfo
         {{.atomTag = android::util::GPU_STATS_GLOBAL_INFO},
          {.puller = new GpuStatsPuller(android::util::GPU_STATS_GLOBAL_INFO)}},
+
         // GpuStatsAppInfo
         {{.atomTag = android::util::GPU_STATS_APP_INFO},
          {.puller = new GpuStatsPuller(android::util::GPU_STATS_APP_INFO)}},
+
         // AppsOnExternalStorageInfo
         {{.atomTag = android::util::APPS_ON_EXTERNAL_STORAGE_INFO},
          {.puller = new StatsCompanionServicePuller(android::util::APPS_ON_EXTERNAL_STORAGE_INFO)}},
+
         // Face Settings
         {{.atomTag = android::util::FACE_SETTINGS},
          {.puller = new StatsCompanionServicePuller(android::util::FACE_SETTINGS)}},
+
         // App ops
         {{.atomTag = android::util::APP_OPS},
          {.puller = new StatsCompanionServicePuller(android::util::APP_OPS)}},
-        // SurfaceflingerStatsGlobalInfo
-        {{.atomTag = android::util::SURFACEFLINGER_STATS_GLOBAL_INFO},
-         {.puller =
-                  new SurfaceflingerStatsPuller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO)}},
+
         // VmsClientStats
         {{.atomTag = android::util::VMS_CLIENT_STATS},
          {.additiveFields = {5, 6, 7, 8, 9, 10},
           .puller = new CarStatsPuller(android::util::VMS_CLIENT_STATS)}},
+
         // NotiifcationRemoteViews.
         {{.atomTag = android::util::NOTIFICATION_REMOTE_VIEWS},
          {.puller = new StatsCompanionServicePuller(android::util::NOTIFICATION_REMOTE_VIEWS)}},
+
         // PermissionStateSampled.
         {{.atomTag = android::util::DANGEROUS_PERMISSION_STATE_SAMPLED},
          {.puller =
diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp b/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp
deleted file mode 100644
index 23b2236..0000000
--- a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "SurfaceflingerStatsPuller.h"
-
-#include <cutils/compiler.h>
-
-#include <numeric>
-
-#include "logd/LogEvent.h"
-#include "stats_log_util.h"
-#include "statslog.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-SurfaceflingerStatsPuller::SurfaceflingerStatsPuller(const int tagId) : StatsPuller(tagId) {
-}
-
-bool SurfaceflingerStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) {
-    switch (mTagId) {
-        case android::util::SURFACEFLINGER_STATS_GLOBAL_INFO:
-            return pullGlobalInfo(data);
-        default:
-            break;
-    }
-
-    return false;
-}
-
-static int64_t getTotalTime(
-        const google::protobuf::RepeatedPtrField<surfaceflinger::SFTimeStatsHistogramBucketProto>&
-                buckets) {
-    int64_t total = 0;
-    for (const auto& bucket : buckets) {
-        if (bucket.time_millis() == 1000) {
-            continue;
-        }
-
-        total += bucket.time_millis() * bucket.frame_count();
-    }
-
-    return total;
-}
-
-bool SurfaceflingerStatsPuller::pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data) {
-    std::string protoBytes;
-    if (CC_UNLIKELY(mStatsProvider)) {
-        protoBytes = mStatsProvider();
-    } else {
-        std::unique_ptr<FILE, decltype(&pclose)> pipe(popen("dumpsys SurfaceFlinger --timestats -dump --proto", "r"), pclose);
-        if (!pipe.get()) {
-            return false;
-        }
-        char buf[1024];
-        size_t bytesRead = 0;
-        do {
-            bytesRead = fread(buf, 1, sizeof(buf), pipe.get());
-            protoBytes.append(buf, bytesRead);
-        } while (bytesRead > 0);
-    }
-    surfaceflinger::SFTimeStatsGlobalProto proto;
-    proto.ParseFromString(protoBytes);
-
-    int64_t totalTime = getTotalTime(proto.present_to_present());
-
-    data->clear();
-    data->reserve(1);
-    std::shared_ptr<LogEvent> event =
-            make_shared<LogEvent>(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, getWallClockNs(),
-                                  getElapsedRealtimeNs());
-    if (!event->write(proto.total_frames())) return false;
-    if (!event->write(proto.missed_frames())) return false;
-    if (!event->write(proto.client_composition_frames())) return false;
-    if (!event->write(proto.display_on_time())) return false;
-    if (!event->write(totalTime)) return false;
-    event->init();
-    data->emplace_back(event);
-
-    return true;
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h b/cmds/statsd/src/external/SurfaceflingerStatsPuller.h
deleted file mode 100644
index ed7153e..0000000
--- a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <timestatsproto/TimeStatsProtoHeader.h>
-
-#include "StatsPuller.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * Pull metrics from Surfaceflinger
- */
-class SurfaceflingerStatsPuller : public StatsPuller {
-public:
-    explicit SurfaceflingerStatsPuller(const int tagId);
-
-    // StatsPuller interface
-    bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override;
-
-protected:
-    // Test-only, for injecting fake data
-    using StatsProvider = std::function<std::string()>;
-    StatsProvider mStatsProvider;
-
-private:
-    bool pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data);
-};
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 35c6d37..e85b975 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -207,10 +207,7 @@
                                      &linkedConditionDimensionKey);
             if (trueConditionDimensions.find(linkedConditionDimensionKey) !=
                     trueConditionDimensions.end()) {
-                for (auto& condIt : whatIt.second) {
-                    condIt.second->onConditionChanged(
-                            currentUnSlicedPartCondition, eventTime);
-                }
+                whatIt.second->onConditionChanged(currentUnSlicedPartCondition, eventTime);
             }
         }
     } else {
@@ -222,15 +219,11 @@
                                          &linkedConditionDimensionKey);
                 if (dimensionsChangedToTrue->find(linkedConditionDimensionKey) !=
                         dimensionsChangedToTrue->end()) {
-                    for (auto& condIt : whatIt.second) {
-                        condIt.second->onConditionChanged(true, eventTime);
-                    }
+                    whatIt.second->onConditionChanged(true, eventTime);
                 }
                 if (dimensionsChangedToFalse->find(linkedConditionDimensionKey) !=
                         dimensionsChangedToFalse->end()) {
-                    for (auto& condIt : whatIt.second) {
-                        condIt.second->onConditionChanged(false, eventTime);
-                    }
+                    whatIt.second->onConditionChanged(false, eventTime);
                 }
             }
         }
@@ -247,9 +240,7 @@
 
     // Now for each of the on-going event, check if the condition has changed for them.
     for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-        for (auto& pair : whatIt.second) {
-            pair.second->onSlicedConditionMayChange(overallCondition, eventTimeNs);
-        }
+        whatIt.second->onSlicedConditionMayChange(overallCondition, eventTimeNs);
     }
 }
 
@@ -283,18 +274,14 @@
         }
 
         for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-            for (auto& pair : whatIt.second) {
-                pair.second->onConditionChanged(mIsActive, eventTimeNs);
-            }
+            whatIt.second->onConditionChanged(mIsActive, eventTimeNs);
         }
     } else if (mIsActive) {
         flushIfNeededLocked(eventTimeNs);
         onSlicedConditionMayChangeInternalLocked(mIsActive, eventTimeNs);
     } else { // mConditionSliced == true && !mIsActive
         for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-            for (auto& pair : whatIt.second) {
-                pair.second->onConditionChanged(mIsActive, eventTimeNs);
-            }
+            whatIt.second->onConditionChanged(mIsActive, eventTimeNs);
         }
     }
 }
@@ -310,9 +297,7 @@
 
     flushIfNeededLocked(eventTime);
     for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-        for (auto& pair : whatIt.second) {
-            pair.second->onConditionChanged(conditionMet, eventTime);
-        }
+        whatIt.second->onConditionChanged(conditionMet, eventTime);
     }
 }
 
@@ -425,19 +410,11 @@
                                                       const int64_t& nextBucketStartTimeNs) {
     for (auto whatIt = mCurrentSlicedDurationTrackerMap.begin();
             whatIt != mCurrentSlicedDurationTrackerMap.end();) {
-        for (auto it = whatIt->second.begin(); it != whatIt->second.end();) {
-            if (it->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) {
-                VLOG("erase bucket for key %s %s", whatIt->first.toString().c_str(),
-                     it->first.toString().c_str());
-                it = whatIt->second.erase(it);
-            } else {
-                ++it;
-            }
-        }
-        if (whatIt->second.empty()) {
+        if (whatIt->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) {
+            VLOG("erase bucket for key %s", whatIt->first.toString().c_str());
             whatIt = mCurrentSlicedDurationTrackerMap.erase(whatIt);
         } else {
-            whatIt++;
+            ++whatIt;
         }
     }
     StatsdStats::getInstance().noteBucketCount(mMetricId);
@@ -453,35 +430,15 @@
             (unsigned long)mCurrentSlicedDurationTrackerMap.size());
     if (verbose) {
         for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-            for (const auto& slice : whatIt.second) {
-                fprintf(out, "\t(what)%s\t(states)%s\n", whatIt.first.toString().c_str(),
-                        slice.first.toString().c_str());
-                slice.second->dumpStates(out, verbose);
-            }
+            fprintf(out, "\t(what)%s\n", whatIt.first.toString().c_str());
+            whatIt.second->dumpStates(out, verbose);
         }
     }
 }
 
 bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
     auto whatIt = mCurrentSlicedDurationTrackerMap.find(newKey.getDimensionKeyInWhat());
-    if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
-        auto stateIt = whatIt->second.find(newKey.getStateValuesKey());
-        if (stateIt != whatIt->second.end()) {
-            return false;
-        }
-        if (whatIt->second.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
-            size_t newTupleCount = whatIt->second.size() + 1;
-            StatsdStats::getInstance().noteMetricDimensionInConditionSize(
-                    mConfigKey, mMetricId, newTupleCount);
-            // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
-            if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
-                ALOGE("DurationMetric %lld dropping data for state values key %s",
-                      (long long)mMetricId, newKey.getStateValuesKey().toString().c_str());
-                StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId);
-                return true;
-            }
-        }
-    } else {
+    if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
         // 1. Report the tuple count if the tuple count > soft limit
         if (mCurrentSlicedDurationTrackerMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
             size_t newTupleCount = mCurrentSlicedDurationTrackerMap.size() + 1;
@@ -503,24 +460,16 @@
                                               const ConditionKey& conditionKeys,
                                               bool condition, const LogEvent& event) {
     const auto& whatKey = eventKey.getDimensionKeyInWhat();
-    const auto& stateKey = eventKey.getStateValuesKey();
 
     auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey);
     if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
         if (hitGuardRailLocked(eventKey)) {
             return;
         }
-        mCurrentSlicedDurationTrackerMap[whatKey][stateKey] = createDurationTracker(eventKey);
-    } else {
-        if (whatIt->second.find(stateKey) == whatIt->second.end()) {
-            if (hitGuardRailLocked(eventKey)) {
-                return;
-            }
-            mCurrentSlicedDurationTrackerMap[whatKey][stateKey] = createDurationTracker(eventKey);
-        }
+        mCurrentSlicedDurationTrackerMap[whatKey] = createDurationTracker(eventKey);
     }
 
-    auto it = mCurrentSlicedDurationTrackerMap.find(whatKey)->second.find(stateKey);
+    auto it = mCurrentSlicedDurationTrackerMap.find(whatKey);
     if (mUseWhatDimensionAsInternalDimension) {
         it->second->noteStart(whatKey, condition,
                               event.GetElapsedTimestampNs(), conditionKeys);
@@ -560,18 +509,14 @@
     // Handles Stopall events.
     if (matcherIndex == mStopAllIndex) {
         for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
-            for (auto& pair : whatIt.second) {
-                pair.second->noteStopAll(event.GetElapsedTimestampNs());
-            }
+            whatIt.second->noteStopAll(event.GetElapsedTimestampNs());
         }
         return;
     }
 
-    HashableDimensionKey dimensionInWhat;
+    HashableDimensionKey dimensionInWhat = DEFAULT_DIMENSION_KEY;
     if (!mDimensionsInWhat.empty()) {
         filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
-    } else {
-       dimensionInWhat = DEFAULT_DIMENSION_KEY;
     }
 
     // Handles Stop events.
@@ -579,9 +524,7 @@
         if (mUseWhatDimensionAsInternalDimension) {
             auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
             if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
-                for (const auto& stateIt : whatIt->second) {
-                    stateIt.second->noteStop(dimensionInWhat, event.GetElapsedTimestampNs(), false);
-                }
+                whatIt->second->noteStop(dimensionInWhat, event.GetElapsedTimestampNs(), false);
             }
             return;
         }
@@ -593,10 +536,7 @@
 
         auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
         if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
-            for (const auto& stateIt : whatIt->second) {
-                stateIt.second->noteStop(internalDimensionKey, event.GetElapsedTimestampNs(),
-                                         false);
-            }
+            whatIt->second->noteStop(internalDimensionKey, event.GetElapsedTimestampNs(), false);
         }
         return;
     }
@@ -619,8 +559,8 @@
 
     condition = condition && mIsActive;
 
-    handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY),
-                     conditionKey, condition, event);
+    handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY), conditionKey,
+                     condition, event);
 }
 
 size_t DurationMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 45908fb..06da0f6 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -132,8 +132,7 @@
     std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>> mPastBuckets;
 
     // The duration trackers in the current bucket.
-    std::unordered_map<HashableDimensionKey,
-        std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>>
+    std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>
             mCurrentSlicedDurationTrackerMap;
 
     // Helper function to create a duration tracker given the metric aggregation type.
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 6b5c299..afe93d4 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -82,8 +82,6 @@
 
     virtual ~DurationTracker(){};
 
-    virtual unique_ptr<DurationTracker> clone(const int64_t eventTime) = 0;
-
     virtual void noteStart(const HashableDimensionKey& key, bool condition,
                            const int64_t eventTime, const ConditionKey& conditionKey) = 0;
     virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index df66cb0..2be5855 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -37,24 +37,6 @@
                       conditionSliced, fullLink, anomalyTrackers) {
 }
 
-unique_ptr<DurationTracker> MaxDurationTracker::clone(const int64_t eventTime) {
-    auto clonedTracker = make_unique<MaxDurationTracker>(*this);
-    for (auto it = clonedTracker->mInfos.begin(); it != clonedTracker->mInfos.end();) {
-        if (it->second.state  != kStopped) {
-            it->second.lastStartTime = eventTime;
-            it->second.lastDuration = 0;
-            it++;
-        } else {
-            it = clonedTracker->mInfos.erase(it);
-        }
-    }
-    if (clonedTracker->mInfos.empty()) {
-        return nullptr;
-    } else {
-        return clonedTracker;
-    }
-}
-
 bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
     // ===========GuardRail==============
     if (mInfos.find(newKey) != mInfos.end()) {
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index d0371da..efb8dc7 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -38,8 +38,6 @@
 
     MaxDurationTracker(const MaxDurationTracker& tracker) = default;
 
-    unique_ptr<DurationTracker> clone(const int64_t eventTime) override;
-
     void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
                    const ConditionKey& conditionKey) override;
     void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index b0fd975..57f3965 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -38,13 +38,6 @@
     mLastStartTime = 0;
 }
 
-unique_ptr<DurationTracker> OringDurationTracker::clone(const int64_t eventTime) {
-    auto clonedTracker = make_unique<OringDurationTracker>(*this);
-    clonedTracker->mLastStartTime = eventTime;
-    clonedTracker->mDuration = 0;
-    return clonedTracker;
-}
-
 bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
     // ===========GuardRail==============
     // 1. Report the tuple count if the tuple count > soft limit
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index 43c48d5..c3aad66 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -37,8 +37,6 @@
 
     OringDurationTracker(const OringDurationTracker& tracker) = default;
 
-    unique_ptr<DurationTracker> clone(const int64_t eventTime) override;
-
     void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
                    const ConditionKey& conditionKey) override;
     void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
index a9a105f..a37cad1 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
@@ -69,12 +69,6 @@
     }
 }
 
-void SubscriberReporter::removeConfig(const ConfigKey& configKey) {
-    VLOG("SubscriberReporter::removeConfig called.");
-    lock_guard<std::mutex> lock(mLock);
-    mIntentMap.erase(configKey);
-}
-
 void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey,
                                                   const Subscription& subscription,
                                                   const MetricDimensionKey& dimKey) const {
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h
index 8ccc8ee..087a1b8 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.h
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.h
@@ -59,9 +59,6 @@
      */
     void unsetBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId);
 
-    /** Remove all information stored by SubscriberReporter about the given config. */
-    void removeConfig(const ConfigKey& configKey);
-
     /**
      * Sends a broadcast via the intentSender previously stored for the
      * given (configKey, subscriberId) pair by setBroadcastSubscriber.
diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
index 0bc3ebb..16b51d9 100644
--- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
@@ -28,16 +28,16 @@
 
 #ifdef __ANDROID__
 
-const string kAndroid = "android";
 const string kApp1 = "app1.sharing.1";
 const int kConfigKey = 789130123;  // Randomly chosen to avoid collisions with existing configs.
+const int kCallingUid = 0; // Randomly chosen
 
 void SendConfig(StatsService& service, const StatsdConfig& config) {
     string str;
     config.SerializeToString(&str);
     std::vector<uint8_t> configAsVec(str.begin(), str.end());
     bool success;
-    service.addConfiguration(kConfigKey, configAsVec, String16(kAndroid.c_str()));
+    service.addConfiguration(kConfigKey, configAsVec, kCallingUid);
 }
 
 ConfigMetricsReport GetReports(sp<StatsLogProcessor> processor, int64_t timestamp,
@@ -50,7 +50,7 @@
     ConfigMetricsReportList reports;
     reports.ParseFromArray(output.data(), output.size());
     EXPECT_EQ(1, reports.reports_size());
-    return reports.reports(0);
+    return reports.reports(kCallingUid);
 }
 
 StatsdConfig MakeConfig() {
diff --git a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
deleted file mode 100644
index 5b7a30d..0000000
--- a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "SurfaceflingerStatsPuller_test"
-
-#include "src/external/SurfaceflingerStatsPuller.h"
-#include "statslog.h"
-
-#include <gtest/gtest.h>
-#include <log/log.h>
-
-#ifdef __ANDROID__
-
-namespace android {
-namespace os {
-namespace statsd {
-
-class TestableSurfaceflingerStatsPuller : public SurfaceflingerStatsPuller {
-public:
-    TestableSurfaceflingerStatsPuller(const int tagId) : SurfaceflingerStatsPuller(tagId){};
-
-    void injectStats(const StatsProvider& statsProvider) {
-        mStatsProvider = statsProvider;
-    }
-};
-
-class SurfaceflingerStatsPullerTest : public ::testing::Test {
-public:
-    SurfaceflingerStatsPullerTest() {
-        const ::testing::TestInfo* const test_info =
-                ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
-    }
-
-    ~SurfaceflingerStatsPullerTest() {
-        const ::testing::TestInfo* const test_info =
-                ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
-    }
-};
-
-TEST_F(SurfaceflingerStatsPullerTest, pullGlobalStats) {
-    surfaceflinger::SFTimeStatsGlobalProto proto;
-    proto.set_total_frames(1);
-    proto.set_missed_frames(2);
-    proto.set_client_composition_frames(2);
-    proto.set_display_on_time(4);
-
-    auto bucketOne = proto.add_present_to_present();
-    bucketOne->set_time_millis(2);
-    bucketOne->set_frame_count(4);
-    auto bucketTwo = proto.add_present_to_present();
-    bucketTwo->set_time_millis(4);
-    bucketTwo->set_frame_count(1);
-    auto bucketThree = proto.add_present_to_present();
-    bucketThree->set_time_millis(1000);
-    bucketThree->set_frame_count(1);
-    static constexpr int64_t expectedAnimationMillis = 12;
-    TestableSurfaceflingerStatsPuller puller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO);
-
-    puller.injectStats([&] {
-        return proto.SerializeAsString();
-    });
-    puller.ForceClearCache();
-    vector<std::shared_ptr<LogEvent>> outData;
-    puller.Pull(&outData);
-
-    ASSERT_EQ(1, outData.size());
-    EXPECT_EQ(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, outData[0]->GetTagId());
-    EXPECT_EQ(proto.total_frames(), outData[0]->getValues()[0].mValue.long_value);
-    EXPECT_EQ(proto.missed_frames(), outData[0]->getValues()[1].mValue.long_value);
-    EXPECT_EQ(proto.client_composition_frames(), outData[0]->getValues()[2].mValue.long_value);
-    EXPECT_EQ(proto.display_on_time(), outData[0]->getValues()[3].mValue.long_value);
-    EXPECT_EQ(expectedAnimationMillis, outData[0]->getValues()[4].mValue.long_value);
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
-#else
-GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 4d0acb3..4aaf727 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -22519,8 +22519,6 @@
 HSPLcom/android/internal/telephony/PhoneFactory;->makeDefaultPhones(Landroid/content/Context;)V
 HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;-><init>(Ljava/lang/String;I)V
 HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;->values()[Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;
-HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;-><init>(Landroid/content/Context;Landroid/os/Handler;)V
-HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;->notifyServiceState(I)V
 HSPLcom/android/internal/telephony/PhoneSubInfoController;->callPhoneMethodWithPermissionCheck(ILjava/lang/String;Ljava/lang/String;Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper;Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper;)Ljava/lang/Object;
 HSPLcom/android/internal/telephony/PhoneSubInfoController;->getCarrierInfoForImsiEncryption(IILjava/lang/String;)Landroid/telephony/ImsiEncryptionInfo;
 HSPLcom/android/internal/telephony/PhoneSubInfoController;->getGroupIdLevel1ForSubscriber(ILjava/lang/String;)Ljava/lang/String;
@@ -37729,7 +37727,6 @@
 Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;
 Lcom/android/internal/telephony/PhoneInternalInterface;
 Lcom/android/internal/telephony/PhoneNotifier;
-Lcom/android/internal/telephony/PhoneStateIntentReceiver;
 Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper;
 Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper;
 Lcom/android/internal/telephony/PhoneSubInfoController;
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 97f009c..e53c74b 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -4841,7 +4841,6 @@
 com.android.internal.telephony.PhoneFactory
 com.android.internal.telephony.PhoneInternalInterface
 com.android.internal.telephony.PhoneNotifier
-com.android.internal.telephony.PhoneStateIntentReceiver
 com.android.internal.telephony.PhoneSubInfoController$CallPhoneMethodHelper
 com.android.internal.telephony.PhoneSubInfoController$PermissionCheckHelper
 com.android.internal.telephony.PhoneSubInfoController
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index c0fee6e..e46840c 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -1547,6 +1547,34 @@
             void onShowModeChanged(@NonNull SoftKeyboardController controller,
                     @SoftKeyboardShowMode int showMode);
         }
+
+        /**
+         * Switches the current IME for the user for whom the service is enabled. The change will
+         * persist until the current IME is explicitly changed again, and may persist beyond the
+         * life cycle of the requesting service.
+         *
+         * @param imeId The ID of the input method to make current. This IME must be installed and
+         *              enabled.
+         * @return {@code true} if the current input method was successfully switched to the input
+         *         method by {@code imeId},
+         *         {@code false} if the input method specified is not installed, not enabled, or
+         *         otherwise not available to become the current IME
+         *
+         * @see android.view.inputmethod.InputMethodInfo#getId()
+         */
+        public boolean switchToInputMethod(@NonNull String imeId) {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.switchToInputMethod(imeId);
+                } catch (RemoteException re) {
+                    throw new RuntimeException(re);
+                }
+            }
+            return false;
+        }
     }
 
     /**
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 4841781..656f87f 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -91,6 +91,8 @@
 
     void setSoftKeyboardCallbackEnabled(boolean enabled);
 
+    boolean switchToInputMethod(String imeId);
+
     boolean isAccessibilityButtonAvailable();
 
     void sendGesture(int sequence, in ParceledListSlice gestureSteps);
diff --git a/core/java/android/annotation/SystemService.java b/core/java/android/annotation/SystemService.java
index 0c5d15e..c05c1ba 100644
--- a/core/java/android/annotation/SystemService.java
+++ b/core/java/android/annotation/SystemService.java
@@ -19,14 +19,12 @@
 import static java.lang.annotation.ElementType.TYPE;
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
-import android.content.Context;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
 /**
  * Description of a system service available through
- * {@link Context#getSystemService(Class)}. This is used to auto-generate
+ * {@link android.content.Context#getSystemService(Class)}. This is used to auto-generate
  * documentation explaining how to obtain a reference to the service.
  *
  * @hide
@@ -36,9 +34,9 @@
 public @interface SystemService {
     /**
      * The string name of the system service that can be passed to
-     * {@link Context#getSystemService(String)}.
+     * {@link android.content.Context#getSystemService(String)}.
      *
-     * @see Context#getSystemServiceName(Class)
+     * @see android.content.Context#getSystemServiceName(Class)
      */
     String value();
 }
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 032e824..4f3e8ec 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -45,8 +45,19 @@
 
     // Access modes for handleIncomingUser.
     public static final int ALLOW_NON_FULL = 0;
+    /**
+     * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS}
+     * if in the same profile group.
+     * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
+     */
     public static final int ALLOW_NON_FULL_IN_PROFILE = 1;
     public static final int ALLOW_FULL_ONLY = 2;
+    /**
+     * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
+     * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} if in the same profile group.
+     * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
+     */
+    public static final int ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE = 3;
 
     /**
      * Verify that calling app has access to the given provider.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a4f6f57..b82a675 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3349,8 +3349,8 @@
     }
 
     @Override
-    public void handleStartActivity(ActivityClientRecord r,
-            PendingTransactionActions pendingActions) {
+    public void handleStartActivity(IBinder token, PendingTransactionActions pendingActions) {
+        final ActivityClientRecord r = mActivities.get(token);
         final Activity activity = r.activity;
         if (r.activity == null) {
             // TODO(lifecycler): What do we do in this case?
@@ -3364,6 +3364,8 @@
             return;
         }
 
+        unscheduleGcIdler();
+
         // Start
         activity.performStart("handleStartActivity");
         r.setState(ON_START);
@@ -3400,6 +3402,9 @@
                                 + " did not call through to super.onPostCreate()");
             }
         }
+
+        updateVisibility(r, true /* show */);
+        mSomeActivitiesChanged = true;
     }
 
     /**
@@ -4660,8 +4665,8 @@
     @UnsupportedAppUsage
     final void performStopActivity(IBinder token, boolean saveState, String reason) {
         ActivityClientRecord r = mActivities.get(token);
-        performStopActivityInner(r, null /* stopInfo */, false /* keepShown */, saveState,
-                false /* finalStateRequest */, reason);
+        performStopActivityInner(r, null /* stopInfo */, saveState, false /* finalStateRequest */,
+                reason);
     }
 
     private static final class ProviderRefCount {
@@ -4687,25 +4692,19 @@
     }
 
     /**
-     * Core implementation of stopping an activity.  Note this is a little
-     * tricky because the server's meaning of stop is slightly different
-     * than our client -- for the server, stop means to save state and give
-     * it the result when it is done, but the window may still be visible.
-     * For the client, we want to call onStop()/onStart() to indicate when
-     * the activity's UI visibility changes.
+     * Core implementation of stopping an activity.
      * @param r Target activity client record.
      * @param info Action that will report activity stop to server.
-     * @param keepShown Flag indicating whether the activity is still shown.
      * @param saveState Flag indicating whether the activity state should be saved.
      * @param finalStateRequest Flag indicating if this call is handling final lifecycle state
      *                          request for a transaction.
      * @param reason Reason for performing this operation.
      */
-    private void performStopActivityInner(ActivityClientRecord r, StopInfo info, boolean keepShown,
+    private void performStopActivityInner(ActivityClientRecord r, StopInfo info,
             boolean saveState, boolean finalStateRequest, String reason) {
         if (localLOGV) Slog.v(TAG, "Performing stop of " + r);
         if (r != null) {
-            if (!keepShown && r.stopped) {
+            if (r.stopped) {
                 if (r.activity.mFinished) {
                     // If we are finishing, we won't call onResume() in certain
                     // cases.  So here we likewise don't want to call onStop()
@@ -4740,9 +4739,7 @@
                 }
             }
 
-            if (!keepShown) {
-                callActivityOnStop(r, saveState, reason);
-            }
+            callActivityOnStop(r, saveState, reason);
         }
     }
 
@@ -4810,20 +4807,19 @@
     }
 
     @Override
-    public void handleStopActivity(IBinder token, boolean show, int configChanges,
+    public void handleStopActivity(IBinder token, int configChanges,
             PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
         final ActivityClientRecord r = mActivities.get(token);
         r.activity.mConfigChangeFlags |= configChanges;
 
         final StopInfo stopInfo = new StopInfo();
-        performStopActivityInner(r, stopInfo, show, true /* saveState */, finalStateRequest,
+        performStopActivityInner(r, stopInfo, true /* saveState */, finalStateRequest,
                 reason);
 
         if (localLOGV) Slog.v(
-            TAG, "Finishing stop of " + r + ": show=" + show
-            + " win=" + r.window);
+            TAG, "Finishing stop of " + r + ": win=" + r.window);
 
-        updateVisibility(r, show);
+        updateVisibility(r, false);
 
         // Make sure any pending writes are now committed.
         if (!r.isPreHoneycomb()) {
@@ -4859,34 +4855,6 @@
         }
     }
 
-    @Override
-    public void handleWindowVisibility(IBinder token, boolean show) {
-        ActivityClientRecord r = mActivities.get(token);
-
-        if (r == null) {
-            Log.w(TAG, "handleWindowVisibility: no activity for token " + token);
-            return;
-        }
-
-        if (!show && !r.stopped) {
-            performStopActivityInner(r, null /* stopInfo */, show, false /* saveState */,
-                    false /* finalStateRequest */, "handleWindowVisibility");
-        } else if (show && r.getLifecycleState() == ON_STOP) {
-            // If we are getting ready to gc after going to the background, well
-            // we are back active so skip it.
-            unscheduleGcIdler();
-
-            r.activity.performRestart(true /* start */, "handleWindowVisibility");
-            r.setState(ON_START);
-        }
-        if (r.activity.mDecor != null) {
-            if (false) Slog.v(
-                TAG, "Handle window " + r + " visibility: " + show);
-            updateVisibility(r, show);
-        }
-        mSomeActivitiesChanged = true;
-    }
-
     // TODO: This method should be changed to use {@link #performStopActivityInner} to perform to
     // stop operation on the activity to reduce code duplication and the chance of fixing a bug in
     // one place and missing the other.
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 4a8e4e2..a11f41f 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -726,7 +726,17 @@
     /** @hide Capture the device's display contents and/or audio */
     @UnsupportedAppUsage
     public static final int OP_PROJECT_MEDIA = 46;
-    /** @hide Activate a VPN connection without user intervention. */
+    /**
+     * Start (without additional user intervention) a VPN connection, as used by {@link
+     * android.net.VpnService} along with as Platform VPN connections, as used by {@link
+     * android.net.VpnManager}
+     *
+     * <p>This appop is granted to apps that have already been given user consent to start
+     * VpnService based VPN connections. As this is a superset of OP_ACTIVATE_PLATFORM_VPN, this
+     * appop also allows the starting of Platform VPNs.
+     *
+     * @hide
+     */
     @UnsupportedAppUsage
     public static final int OP_ACTIVATE_VPN = 47;
     /** @hide Access the WallpaperManagerAPI to write wallpapers. */
@@ -852,10 +862,21 @@
     public static final int OP_MANAGE_EXTERNAL_STORAGE = 92;
     /** @hide Communicate cross-profile within the same profile group. */
     public static final int OP_INTERACT_ACROSS_PROFILES = 93;
+    /**
+     * Start (without additional user intervention) a Platform VPN connection, as used by {@link
+     * android.net.VpnManager}
+     *
+     * <p>This appop is granted to apps that have already been given user consent to start Platform
+     * VPN connections. This appop is insufficient to start VpnService based VPNs; OP_ACTIVATE_VPN
+     * is needed for that.
+     *
+     * @hide
+     */
+    public static final int OP_ACTIVATE_PLATFORM_VPN = 94;
 
     /** @hide */
     @UnsupportedAppUsage
-    public static final int _NUM_OP = 94;
+    public static final int _NUM_OP = 95;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1149,6 +1170,8 @@
     /** @hide Communicate cross-profile within the same profile group. */
     @SystemApi
     public static final String OPSTR_INTERACT_ACROSS_PROFILES = "android:interact_across_profiles";
+    /** @hide Start Platform VPN without user intervention */
+    public static final String OPSTR_ACTIVATE_PLATFORM_VPN = "android:activate_platform_vpn";
 
 
     /** {@link #sAppOpsToNote} not initialized yet for this op */
@@ -1333,6 +1356,7 @@
             OP_QUERY_ALL_PACKAGES,              // QUERY_ALL_PACKAGES
             OP_MANAGE_EXTERNAL_STORAGE,         // MANAGE_EXTERNAL_STORAGE
             OP_INTERACT_ACROSS_PROFILES,        //INTERACT_ACROSS_PROFILES
+            OP_ACTIVATE_PLATFORM_VPN,           // ACTIVATE_PLATFORM_VPN
     };
 
     /**
@@ -1433,6 +1457,7 @@
             OPSTR_QUERY_ALL_PACKAGES,
             OPSTR_MANAGE_EXTERNAL_STORAGE,
             OPSTR_INTERACT_ACROSS_PROFILES,
+            OPSTR_ACTIVATE_PLATFORM_VPN,
     };
 
     /**
@@ -1533,7 +1558,8 @@
             "ACCESS_MEDIA_LOCATION",
             "QUERY_ALL_PACKAGES",
             "MANAGE_EXTERNAL_STORAGE",
-            "INTERACT_ACROSS_PROFILES"
+            "INTERACT_ACROSS_PROFILES",
+            "ACTIVATE_PLATFORM_VPN",
     };
 
     /**
@@ -1636,6 +1662,7 @@
             null, // no permission for OP_QUERY_ALL_PACKAGES
             Manifest.permission.MANAGE_EXTERNAL_STORAGE,
             android.Manifest.permission.INTERACT_ACROSS_PROFILES,
+            null, // no permission for OP_ACTIVATE_PLATFORM_VPN
     };
 
     /**
@@ -1738,6 +1765,7 @@
             null, // QUERY_ALL_PACKAGES
             null, // MANAGE_EXTERNAL_STORAGE
             null, // INTERACT_ACROSS_PROFILES
+            null, // ACTIVATE_PLATFORM_VPN
     };
 
     /**
@@ -1839,6 +1867,7 @@
             false, // QUERY_ALL_PACKAGES
             false, // MANAGE_EXTERNAL_STORAGE
             false, // INTERACT_ACROSS_PROFILES
+            false, // ACTIVATE_PLATFORM_VPN
     };
 
     /**
@@ -1939,6 +1968,7 @@
             AppOpsManager.MODE_DEFAULT, // QUERY_ALL_PACKAGES
             AppOpsManager.MODE_DEFAULT, // MANAGE_EXTERNAL_STORAGE
             AppOpsManager.MODE_DEFAULT, // INTERACT_ACROSS_PROFILES
+            AppOpsManager.MODE_IGNORED, // ACTIVATE_PLATFORM_VPN
     };
 
     /**
@@ -2043,6 +2073,7 @@
             false, // QUERY_ALL_PACKAGES
             false, // MANAGE_EXTERNAL_STORAGE
             false, // INTERACT_ACROSS_PROFILES
+            false, // ACTIVATE_PLATFORM_VPN
     };
 
     /**
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
index d993ec1..3febf71 100644
--- a/core/java/android/app/AsyncNotedAppOp.java
+++ b/core/java/android/app/AsyncNotedAppOp.java
@@ -256,10 +256,10 @@
     };
 
     @DataClass.Generated(
-            time = 1578321462996L,
+            time = 1578516519372L,
             codegenVersion = "1.0.14",
             sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
-            inputSignatures = "private final @android.annotation.IntRange(from=0L, to=93L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
+            inputSignatures = "private final @android.annotation.IntRange(from=0L, to=94L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index f9a689a..d2235f1 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -119,7 +119,6 @@
     /**
      * Stop the activity.
      * @param token Target activity token.
-     * @param show Flag indicating whether activity is still shown.
      * @param configChanges Activity configuration changes.
      * @param pendingActions Pending actions to be used on this or later stages of activity
      *                       transaction.
@@ -127,7 +126,7 @@
      *                          request for a transaction.
      * @param reason Reason for performing this operation.
      */
-    public abstract void handleStopActivity(IBinder token, boolean show, int configChanges,
+    public abstract void handleStopActivity(IBinder token, int configChanges,
             PendingTransactionActions pendingActions, boolean finalStateRequest, String reason);
 
     /** Report that activity was stopped to server. */
@@ -161,15 +160,12 @@
     /** Request that an activity enter picture-in-picture. */
     public abstract void handlePictureInPictureRequested(IBinder token);
 
-    /** Update window visibility. */
-    public abstract void handleWindowVisibility(IBinder token, boolean show);
-
     /** Perform activity launch. */
     public abstract Activity handleLaunchActivity(ActivityThread.ActivityClientRecord r,
             PendingTransactionActions pendingActions, Intent customIntent);
 
     /** Perform activity start. */
-    public abstract void handleStartActivity(ActivityThread.ActivityClientRecord r,
+    public abstract void handleStartActivity(IBinder token,
             PendingTransactionActions pendingActions);
 
     /** Get package info. */
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 49c389a..1278ff6 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -16,7 +16,9 @@
 
 package android.app;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
@@ -40,11 +42,13 @@
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.provider.BaseColumns;
 import android.provider.Downloads;
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.text.TextUtils;
+import android.util.LongSparseArray;
 import android.util.Pair;
 
 import java.io.File;
@@ -1069,6 +1073,37 @@
     }
 
     /**
+     * Notify {@link DownloadManager} that the given {@link MediaStore} items
+     * were just deleted so that {@link DownloadManager} internal data
+     * structures can be cleaned up.
+     *
+     * @param idToMime map from {@link BaseColumns#_ID} to
+     *            {@link ContentResolver#getType(Uri)}.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
+    public void onMediaStoreDownloadsDeleted(@NonNull LongSparseArray<String> idToMime) {
+        try (ContentProviderClient client = mResolver
+                .acquireUnstableContentProviderClient(mBaseUri)) {
+           final Bundle callExtras = new Bundle();
+           final long[] ids = new long[idToMime.size()];
+           final String[] mimeTypes = new String[idToMime.size()];
+           for (int i = idToMime.size() - 1; i >= 0; --i) {
+               ids[i] = idToMime.keyAt(i);
+               mimeTypes[i] = idToMime.valueAt(i);
+           }
+           callExtras.putLongArray(android.provider.Downloads.EXTRA_IDS, ids);
+           callExtras.putStringArray(android.provider.Downloads.EXTRA_MIME_TYPES,
+                   mimeTypes);
+           client.call(android.provider.Downloads.CALL_MEDIASTORE_DOWNLOADS_DELETED,
+                   null, callExtras);
+        } catch (RemoteException e) {
+            // Should not happen
+        }
+    }
+
+    /**
      * Enqueue a new download.  The download will start automatically once the download manager is
      * ready to execute it and connectivity is available.
      *
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 700b3c1..e5c046c 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -68,6 +68,7 @@
 import android.os.WorkSource;
 import android.service.voice.IVoiceInteractionSession;
 import android.view.IRecentsAnimationRunner;
+import android.view.ITaskOrganizer;
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationAdapter;
 import android.view.WindowContainerTransaction;
@@ -121,6 +122,9 @@
             in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho,
             int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options,
             IBinder permissionToken, boolean ignoreTargetSecurity, int userId);
+
+    void registerTaskOrganizer(in ITaskOrganizer organizer, int windowingMode);
+
     boolean isActivityStartAllowedOnDisplay(int displayId, in Intent intent, in String resolvedType,
             int userId);
 
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 86f52af..fcdb7cc 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -94,8 +94,11 @@
     void updateNotificationChannelGroupForPackage(String pkg, int uid, in NotificationChannelGroup group);
     void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel);
     NotificationChannel getNotificationChannel(String callingPkg, int userId, String pkg, String channelId);
+    NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, String conversationId);
+    void createConversationNotificationChannelForPackage(String pkg, int uid, in NotificationChannel parentChannel, String conversationId);
     NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted);
     void deleteNotificationChannel(String pkg, String channelId);
+    void deleteConversationNotificationChannels(String pkg, int uid, String conversationId);
     ParceledListSlice getNotificationChannels(String callingPkg, String targetPkg, int userId);
     ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
     int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 4033aea..7cdf85e 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -177,7 +177,7 @@
                 pendingActions = null;
             }
 
-            mActivityThread.handleStartActivity(clientRecord, pendingActions);
+            mActivityThread.handleStartActivity(r, pendingActions);
             r.curState = STARTED;
             
             if (desiredState == RESUMED) {
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 3eee1ae..a33c2c1 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -23,6 +23,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ShortcutInfo;
 import android.media.AudioAttributes;
 import android.net.Uri;
 import android.os.Parcel;
@@ -57,6 +58,22 @@
     public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
 
     /**
+     * The formatter used by the system to create an id for notification
+     * channels when it automatically creates conversation channels on behalf of an app. The format
+     * string takes two arguments, in this order: the
+     * {@link #getId()} of the original notification channel, and the
+     * {@link ShortcutInfo#getId() id} of the conversation.
+     */
+    public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s";
+
+    /**
+     * TODO: STOPSHIP  remove
+     * Conversation id to use for apps that aren't providing them yet.
+     * @hide
+     */
+    public static final String PLACEHOLDER_CONVERSATION_ID = "placeholder_id";
+
+    /**
      * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
      * limit.
      */
@@ -85,6 +102,8 @@
     private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system";
     private static final String ATT_ALLOW_BUBBLE = "can_bubble";
     private static final String ATT_ORIG_IMP = "orig_imp";
+    private static final String ATT_PARENT_CHANNEL = "parent";
+    private static final String ATT_CONVERSATION_ID = "conv_id";
     private static final String DELIMITER = ",";
 
     /**
@@ -147,7 +166,7 @@
     private static final boolean DEFAULT_ALLOW_BUBBLE = true;
 
     @UnsupportedAppUsage
-    private final String mId;
+    private String mId;
     private String mName;
     private String mDesc;
     private int mImportance = DEFAULT_IMPORTANCE;
@@ -172,6 +191,8 @@
     private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE;
     private boolean mImportanceLockedByOEM;
     private boolean mImportanceLockedDefaultApp;
+    private String mParentId = null;
+    private String mConversationId = null;
 
     /**
      * Creates a notification channel.
@@ -236,6 +257,8 @@
         mAllowBubbles = in.readBoolean();
         mImportanceLockedByOEM = in.readBoolean();
         mOriginalImportance = in.readInt();
+        mParentId = in.readString();
+        mConversationId = in.readString();
     }
 
     @Override
@@ -291,6 +314,8 @@
         dest.writeBoolean(mAllowBubbles);
         dest.writeBoolean(mImportanceLockedByOEM);
         dest.writeInt(mOriginalImportance);
+        dest.writeString(mParentId);
+        dest.writeString(mConversationId);
     }
 
     /**
@@ -363,6 +388,13 @@
     // Modifiable by apps on channel creation.
 
     /**
+     * @hide
+     */
+    public void setId(String id) {
+        mId = id;
+    }
+
+    /**
      * Sets what group this channel belongs to.
      *
      * Group information is only used for presentation, not for behavior.
@@ -502,6 +534,23 @@
     }
 
     /**
+     * Sets this channel as being person-centric. Different settings and functionality may be
+     * exposed for people-centric channels.
+     *
+     * @param parentChannelId The {@link #getId()} id} of the generic channel that notifications of
+     *                        this type would be posted to in absence of a specific conversation id.
+     *                        For example, if this channel represents 'Messages from Person A', the
+     *                        parent channel would be 'Messages.'
+     * @param conversationId The {@link ShortcutInfo#getId()} of the shortcut representing this
+     *                       channel's conversation.
+     */
+    public void setConversationId(@Nullable String parentChannelId,
+            @Nullable String conversationId) {
+        mParentId = parentChannelId;
+        mConversationId = conversationId;
+    }
+
+    /**
      * Returns the id of this channel.
      */
     public String getId() {
@@ -622,6 +671,22 @@
     }
 
     /**
+     * Returns the {@link #getId() id} of the parent notification channel to this channel, if it's
+     * a conversation related channel. See {@link #setConversationId(String, String)}.
+     */
+    public @Nullable String getParentChannelId() {
+        return mParentId;
+    }
+
+    /**
+     * Returns the {@link ShortcutInfo#getId() id} of the conversation backing this channel, if it's
+     * associated with a conversation. See {@link #setConversationId(String, String)}.
+     */
+    public @Nullable String getConversationId() {
+        return mConversationId;
+    }
+
+    /**
      * @hide
      */
     @SystemApi
@@ -761,6 +826,8 @@
         setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
         setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
         setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE));
+        setConversationId(parser.getAttributeValue(ATT_PARENT_CHANNEL, null),
+                parser.getAttributeValue(ATT_CONVERSATION_ID, null));
     }
 
     @Nullable
@@ -885,6 +952,12 @@
         if (getOriginalImportance() != DEFAULT_IMPORTANCE) {
             out.attribute(null, ATT_ORIG_IMP, Integer.toString(getOriginalImportance()));
         }
+        if (getParentChannelId() != null) {
+            out.attribute(null, ATT_PARENT_CHANNEL, getParentChannelId());
+        }
+        if (getConversationId() != null) {
+            out.attribute(null, ATT_CONVERSATION_ID, getConversationId());
+        }
 
         // mImportanceLockedDefaultApp and mImportanceLockedByOEM have a different source of
         // truth and so aren't written to this xml file
@@ -1042,7 +1115,9 @@
                 && Objects.equals(getAudioAttributes(), that.getAudioAttributes())
                 && mImportanceLockedByOEM == that.mImportanceLockedByOEM
                 && mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp
-                && mOriginalImportance == that.mOriginalImportance;
+                && mOriginalImportance == that.mOriginalImportance
+                && Objects.equals(getParentChannelId(), that.getParentChannelId())
+                && Objects.equals(getConversationId(), that.getConversationId());
     }
 
     @Override
@@ -1052,7 +1127,8 @@
                 getUserLockedFields(),
                 isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(),
                 getAudioAttributes(), isBlockableSystem(), mAllowBubbles,
-                mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance);
+                mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance,
+                mParentId, mConversationId);
         result = 31 * result + Arrays.hashCode(mVibration);
         return result;
     }
@@ -1063,26 +1139,7 @@
         String output = "NotificationChannel{"
                 + "mId='" + mId + '\''
                 + ", mName=" + redactedName
-                + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
-                + ", mImportance=" + mImportance
-                + ", mBypassDnd=" + mBypassDnd
-                + ", mLockscreenVisibility=" + mLockscreenVisibility
-                + ", mSound=" + mSound
-                + ", mLights=" + mLights
-                + ", mLightColor=" + mLightColor
-                + ", mVibration=" + Arrays.toString(mVibration)
-                + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields)
-                + ", mFgServiceShown=" + mFgServiceShown
-                + ", mVibrationEnabled=" + mVibrationEnabled
-                + ", mShowBadge=" + mShowBadge
-                + ", mDeleted=" + mDeleted
-                + ", mGroup='" + mGroup + '\''
-                + ", mAudioAttributes=" + mAudioAttributes
-                + ", mBlockableSystem=" + mBlockableSystem
-                + ", mAllowBubbles=" + mAllowBubbles
-                + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
-                + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
-                + ", mOriginalImp=" + mOriginalImportance
+                + getFieldsString()
                 + '}';
         pw.println(prefix + output);
     }
@@ -1092,7 +1149,12 @@
         return "NotificationChannel{"
                 + "mId='" + mId + '\''
                 + ", mName=" + mName
-                + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
+                + getFieldsString()
+                + '}';
+    }
+
+    private String getFieldsString() {
+        return  ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
                 + ", mImportance=" + mImportance
                 + ", mBypassDnd=" + mBypassDnd
                 + ", mLockscreenVisibility=" + mLockscreenVisibility
@@ -1112,7 +1174,8 @@
                 + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
                 + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
                 + ", mOriginalImp=" + mOriginalImportance
-                + '}';
+                + ", mParent=" + mParentId
+                + ", mConversationId" + mConversationId;
     }
 
     /** @hide */
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index fdbb8bb..61c1098 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -843,6 +843,25 @@
     }
 
     /**
+     * Returns the notification channel settings for a given channel and conversation id.
+     *
+     * <p>The channel must belong to your package, or to a package you are an approved notification
+     * delegate for (see {@link #canNotifyAsPackage(String)}), or it will not be returned. To query
+     * a channel as a notification delegate, call this method from a context created for that
+     * package (see {@link Context#createPackageContext(String, int)}).</p>
+     */
+    public @Nullable NotificationChannel getNotificationChannel(@NonNull String channelId,
+            @NonNull String conversationId) {
+        INotificationManager service = getService();
+        try {
+            return service.getConversationNotificationChannel(mContext.getOpPackageName(),
+                    mContext.getUserId(), mContext.getPackageName(), channelId, conversationId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns all notification channels belonging to the calling package.
      *
      * <p>Approved notification delegates (see {@link #canNotifyAsPackage(String)}) can query
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 844e72e..736efb6 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -15,6 +15,7 @@
  */
 
 package android.app;
+
 import android.annotation.NonNull;
 import android.os.SystemProperties;
 import android.util.Log;
@@ -23,6 +24,7 @@
 
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Random;
 import java.util.concurrent.atomic.AtomicLong;
 
@@ -164,6 +166,7 @@
     private static final String TAG = "PropertyInvalidatedCache";
     private static final boolean DEBUG = false;
     private static final boolean ENABLE = true;
+    private static final boolean VERIFY = false;
 
     private final Object mLock = new Object();
 
@@ -228,6 +231,18 @@
     protected abstract Result recompute(Query query);
 
     /**
+     * Determines if a pair of responses are considered equal. Used to determine whether
+     * a cache is inadvertently returning stale results when VERIFY is set to true.
+     */
+    protected boolean debugCompareQueryResults(Result cachedResult, Result fetchedResult) {
+        // If a service crashes and returns a null result, the cached value remains valid.
+        if (fetchedResult != null) {
+            return Objects.equals(cachedResult, fetchedResult);
+        }
+        return true;
+    }
+
+    /**
      * Make result up-to-date on a cache hit.  Called unlocked;
      * may block.
      *
@@ -334,12 +349,12 @@
                             mCache.put(query, refreshedResult);
                         }
                     }
-                    return refreshedResult;
+                    return maybeCheckConsistency(query, refreshedResult);
                 }
                 if (DEBUG) {
                     Log.d(TAG, "cache hit for " + query);
                 }
-                return cachedResult;
+                return maybeCheckConsistency(query, cachedResult);
             }
             // Cache miss: make the value from scratch.
             if (DEBUG) {
@@ -353,7 +368,7 @@
                     mCache.put(query, result);
                 }
             }
-            return result;
+            return maybeCheckConsistency(query, result);
         }
     }
 
@@ -425,4 +440,15 @@
         }
         SystemProperties.set(name, newValueString);
     }
+
+    private Result maybeCheckConsistency(Query query, Result proposedResult) {
+        if (VERIFY) {
+            Result resultToCompare = recompute(query);
+            boolean nonceChanged = (getCurrentNonce() != mLastSeenNonce);
+            if (!nonceChanged && !debugCompareQueryResults(proposedResult, resultToCompare)) {
+                throw new AssertionError("cache returned out of date response for " + query);
+            }
+        }
+        return proposedResult;
+    }
 }
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index aa11598..d23754e 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -482,19 +482,6 @@
             }
         }
 
-        if (key.mOverlayDirs != null) {
-            for (final String idmapPath : key.mOverlayDirs) {
-                try {
-                    builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
-                            true /*overlay*/));
-                } catch (IOException e) {
-                    Log.w(TAG, "failed to add overlay path " + idmapPath);
-
-                    // continue.
-                }
-            }
-        }
-
         if (key.mLibDirs != null) {
             for (final String libDir : key.mLibDirs) {
                 if (libDir.endsWith(".apk")) {
@@ -513,6 +500,19 @@
             }
         }
 
+        if (key.mOverlayDirs != null) {
+            for (final String idmapPath : key.mOverlayDirs) {
+                try {
+                    builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
+                            true /*overlay*/));
+                } catch (IOException e) {
+                    Log.w(TAG, "failed to add overlay path " + idmapPath);
+
+                    // continue.
+                }
+            }
+        }
+
         return builder.build();
     }
 
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
index 83d1de6..dde6dda 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -26,7 +26,6 @@
 import android.os.IBinder;
 import android.os.IPullAtomCallback;
 import android.os.IPullAtomResultReceiver;
-import android.os.IStatsCompanionService;
 import android.os.IStatsManagerService;
 import android.os.IStatsPullerCallback;
 import android.os.IStatsd;
@@ -61,9 +60,6 @@
     private IStatsd mService;
 
     @GuardedBy("sLock")
-    private IStatsCompanionService mStatsCompanion;
-
-    @GuardedBy("sLock")
     private IStatsManagerService mStatsManagerService;
 
     /**
@@ -157,11 +153,11 @@
     public void addConfig(long configKey, byte[] config) throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsd service = getIStatsdLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 // can throw IllegalArgumentException
                 service.addConfiguration(configKey, config, mContext.getOpPackageName());
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to connect to statsd when adding configuration");
+                Slog.e(TAG, "Failed to connect to statsmanager when adding configuration");
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
                 throw new StatsUnavailableException(e.getMessage(), e);
@@ -194,10 +190,10 @@
     public void removeConfig(long configKey) throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsd service = getIStatsdLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 service.removeConfiguration(configKey, mContext.getOpPackageName());
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to connect to statsd when removing configuration");
+                Slog.e(TAG, "Failed to connect to statsmanager when removing configuration");
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
                 throw new StatsUnavailableException(e.getMessage(), e);
@@ -390,10 +386,10 @@
     public byte[] getReports(long configKey) throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsd service = getIStatsdLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 return service.getData(configKey, mContext.getOpPackageName());
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to connect to statsd when getting data");
+                Slog.e(TAG, "Failed to connect to statsmanager when getting data");
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
                 throw new StatsUnavailableException(e.getMessage(), e);
@@ -427,10 +423,10 @@
     public byte[] getStatsMetadata() throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsd service = getIStatsdLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 return service.getMetadata(mContext.getOpPackageName());
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to connect to statsd when getting metadata");
+                Slog.e(TAG, "Failed to connect to statsmanager when getting metadata");
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
                 throw new StatsUnavailableException(e.getMessage(), e);
@@ -462,21 +458,19 @@
             throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsd service = getIStatsdLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 if (service == null) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Failed to find statsd when getting experiment IDs");
-                    }
-                    return new long[0];
+                    throw new StatsUnavailableException("Failed to find statsmanager when "
+                                                              + "getting experiment IDs");
                 }
                 return service.getRegisteredExperimentIds();
             } catch (RemoteException e) {
                 if (DEBUG) {
                     Slog.d(TAG,
-                            "Failed to connect to StatsCompanionService when getting "
+                            "Failed to connect to StatsManagerService when getting "
                                     + "registered experiment IDs");
                 }
-                return new long[0];
+                throw new StatsUnavailableException("could not connect", e);
             }
         }
     }
@@ -540,7 +534,7 @@
         }
         synchronized (sLock) {
             try {
-                IStatsCompanionService service = getIStatsCompanionServiceLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 PullAtomCallbackInternal rec =
                     new PullAtomCallbackInternal(atomTag, callback, executor);
                 service.registerPullAtomCallback(atomTag, coolDownNs, timeoutNs, additiveFields,
@@ -562,7 +556,7 @@
     public void unregisterPullAtomCallback(int atomTag) {
         synchronized (sLock) {
             try {
-                IStatsCompanionService service = getIStatsCompanionServiceLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 service.unregisterPullAtomCallback(atomTag);
             } catch (RemoteException e) {
                 throw new RuntimeException("Unable to unregister pull atom callback");
@@ -748,16 +742,6 @@
     }
 
     @GuardedBy("sLock")
-    private IStatsCompanionService getIStatsCompanionServiceLocked() {
-        if (mStatsCompanion != null) {
-            return mStatsCompanion;
-        }
-        mStatsCompanion = IStatsCompanionService.Stub.asInterface(
-                ServiceManager.getService("statscompanion"));
-        return mStatsCompanion;
-    }
-
-    @GuardedBy("sLock")
     private IStatsManagerService getIStatsManagerServiceLocked() {
         if (mStatsManagerService != null) {
             return mStatsManagerService;
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index a1765c8..42563b5 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -153,6 +153,11 @@
      */
     public static final int DEFAULT_SETUP_DISABLE2_FLAGS = DISABLE2_ROTATE_SUGGESTIONS;
 
+    /**
+     * disable flags to be applied when the device is sim-locked.
+     */
+    private static final int DEFAULT_SIM_LOCKED_DISABLED_FLAGS = DISABLE_EXPAND;
+
     /** @hide */
     public static final int NAVIGATION_HINT_BACK_ALT      = 1 << 0;
     /** @hide */
@@ -262,6 +267,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @TestApi
     public void expandNotificationsPanel() {
         try {
             final IStatusBarService svc = getService();
@@ -279,6 +285,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @TestApi
     public void collapsePanels() {
         try {
             final IStatusBarService svc = getService();
@@ -385,6 +392,30 @@
     }
 
     /**
+     * Enable or disable expansion of the status bar. When the device is SIM-locked, the status
+     * bar should not be expandable.
+     *
+     * @param disabled If {@code true}, the status bar will be set to non-expandable. If
+     *                 {@code false}, re-enables expansion of the status bar.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.STATUS_BAR)
+    public void setDisabledForSimNetworkLock(boolean disabled) {
+        try {
+            final int userId = Binder.getCallingUserHandle().getIdentifier();
+            final IStatusBarService svc = getService();
+            if (svc != null) {
+                svc.disableForUser(disabled ? DEFAULT_SIM_LOCKED_DISABLED_FLAGS : DISABLE_NONE,
+                        mToken, mContext.getPackageName(), userId);
+            }
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Get this app's currently requested disabled components
      *
      * @return a new DisableInfo
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index ca3d0d7..96664eb 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -170,6 +170,7 @@
 import android.service.persistentdata.PersistentDataBlockManager;
 import android.service.vr.IVrManager;
 import android.telecom.TelecomManager;
+import android.telephony.MmsManager;
 import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.TelephonyRegistryManager;
 import android.util.ArrayMap;
@@ -631,6 +632,13 @@
                 return new TelecomManager(ctx.getOuterContext());
             }});
 
+        registerService(Context.MMS_SERVICE, MmsManager.class,
+                new CachedServiceFetcher<MmsManager>() {
+                    @Override
+                    public MmsManager createService(ContextImpl ctx) {
+                        return new MmsManager(ctx.getOuterContext());
+                    }});
+
         registerService(Context.UI_MODE_SERVICE, UiModeManager.class,
                 new CachedServiceFetcher<UiModeManager>() {
             @Override
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index eaea226..69640b8 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -88,7 +88,6 @@
 import android.util.ArraySet;
 import android.util.Log;
 
-import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.Preconditions;
@@ -399,6 +398,42 @@
             "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
 
     /**
+     * Activity action: Starts the provisioning flow which sets up a financed device.
+     *
+     * <p>During financed device provisioning, a device admin app is downloaded and set as the owner
+     * of the device. A device owner has full control over the device. The device owner can not be
+     * modified by the user.
+     *
+     * <p>A typical use case would be a device that is bought from the reseller through financing
+     * program.
+     *
+     * <p>An intent with this action can be sent only on an unprovisioned device.
+     *
+     * <p>Unlike {@link #ACTION_PROVISION_MANAGED_DEVICE}, the provisioning message can only be sent
+     * by a privileged app with the permission
+     * {@link android.Manifest.permission#DISPATCH_PROVISIONING_MESSAGE}.
+     *
+     * <p>The provisioning intent contains the following properties:
+     * <ul>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_SUPPORT_URL}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_ORGANIZATION_NAME}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
+     * </ul>
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_PROVISION_FINANCED_DEVICE =
+            "android.app.action.PROVISION_FINANCED_DEVICE";
+
+    /**
      * Activity action: Starts the provisioning flow which sets up a managed device.
      * Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}.
      *
@@ -865,6 +900,7 @@
      * The name is displayed only during provisioning.
      *
      * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+     * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
      *
      * @hide
      */
@@ -877,6 +913,7 @@
      * during provisioning. If the url is not HTTPS, an error will be shown.
      *
      * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+     * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
      *
      * @hide
      */
@@ -889,6 +926,7 @@
      * as the app label of the package.
      *
      * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+     * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
      *
      * @hide
      */
@@ -913,6 +951,7 @@
      * {@link android.content.ClipData} of the intent too.
      *
      * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+     * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
      *
      * @hide
      */
@@ -6821,6 +6860,34 @@
     }
 
     /**
+     * @hide
+     * Privileged apps can use this method to find out if the device was provisioned as
+     * organization-owend device with a managed profile.
+     *
+     * This, together with checking whether the device has a device owner (by calling
+     * {@link #isDeviceManaged()}), could be used to learn whether the device is owned by an
+     * organization or an individual:
+     * If this method returns true OR {@link #isDeviceManaged()} returns true, then
+     * the device is owned by an organization. Otherwise, it's owned by an individual.
+     *
+     * @return {@code true} if the device was provisioned as organization-owned device,
+     * {@code false} otherwise.
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean isOrganizationOwnedDeviceWithManagedProfile() {
+        throwIfParentInstance("isOrganizationOwnedDeviceWithManagedProfile");
+        if (mService != null) {
+            try {
+                return mService.isOrganizationOwnedDeviceWithManagedProfile();
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
      * Returns whether the specified package can read the device identifiers.
      *
      * @param packageName The package name of the app to check for device identifier access.
@@ -11279,12 +11346,12 @@
      * #setCrossProfilePackages(ComponentName, Set)}.</li>
      * <li>The default package names set by the OEM that are allowed to request user consent for
      * cross-profile communication without being explicitly enabled by the admin, via
-     * {@link R.array#cross_profile_apps}</li>
+     * {@link com.android.internal.R.array#cross_profile_apps}</li>
      * </ul>
      *
      * @return the combined set of whitelisted package names set via
      * {@link #setCrossProfilePackages(ComponentName, Set)} and
-     * {@link R.array#cross_profile_apps}
+     * {@link com.android.internal.R.array#cross_profile_apps}
      *
      * @hide
      */
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index f299d45..e6c89d9 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -17,9 +17,12 @@
 package android.app.admin;
 
 import android.annotation.UserIdInt;
+import android.content.ComponentName;
 import android.content.Intent;
+import android.os.UserHandle;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * Device policy manager local system service interface.
@@ -165,4 +168,23 @@
      * Do not call it directly. Use {@link DevicePolicyCache#getInstance()} instead.
      */
     protected abstract DeviceStateCache getDeviceStateCache();
+
+    /**
+     * Returns the combined set of the following:
+     * <ul>
+     * <li>The package names that the admin has previously set as allowed to request user consent
+     * for cross-profile communication, via {@link
+     * DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.</li>
+     * <li>The default package names that are allowed to request user consent for cross-profile
+     * communication without being explicitly enabled by the admin , via {@link
+     * DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)}.</li>
+     * </ul>
+     *
+     * @return the combined set of whitelisted package names set via
+     * {@link DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)} and
+     * {@link DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)}
+     *
+     * @hide
+     */
+    public abstract List<String> getAllCrossProfilePackages();
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 92d6ba2..21c9eb5 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -160,6 +160,7 @@
     void setProfileName(in ComponentName who, String profileName);
     void clearProfileOwner(in ComponentName who);
     boolean hasUserSetupCompleted();
+    boolean isOrganizationOwnedDeviceWithManagedProfile();
 
     boolean checkDeviceIdentifierAccess(in String packageName, int pid, int uid);
 
diff --git a/core/java/android/app/servertransaction/StartActivityItem.java b/core/java/android/app/servertransaction/StartActivityItem.java
new file mode 100644
index 0000000..4fbe02b
--- /dev/null
+++ b/core/java/android/app/servertransaction/StartActivityItem.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Request to move an activity to started and visible state.
+ * @hide
+ */
+public class StartActivityItem extends ActivityLifecycleItem {
+
+    private static final String TAG = "StartActivityItem";
+
+    @Override
+    public void execute(ClientTransactionHandler client, IBinder token,
+            PendingTransactionActions pendingActions) {
+        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "startActivityItem");
+        client.handleStartActivity(token, pendingActions);
+        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+    @Override
+    public int getTargetState() {
+        return ON_START;
+    }
+
+
+    // ObjectPoolItem implementation
+
+    private StartActivityItem() {}
+
+    /** Obtain an instance initialized with provided params. */
+    public static StartActivityItem obtain() {
+        StartActivityItem instance = ObjectPool.obtain(StartActivityItem.class);
+        if (instance == null) {
+            instance = new StartActivityItem();
+        }
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        super.recycle();
+        ObjectPool.recycle(this);
+    }
+
+
+    // Parcelable implementation
+
+    /** Write to Parcel. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        // Empty
+    }
+
+    /** Read from Parcel. */
+    private StartActivityItem(Parcel in) {
+        // Empty
+    }
+
+    public static final @android.annotation.NonNull Creator<StartActivityItem> CREATOR =
+            new Creator<StartActivityItem>() {
+                public StartActivityItem createFromParcel(Parcel in) {
+                    return new StartActivityItem(in);
+                }
+
+                public StartActivityItem[] newArray(int size) {
+                    return new StartActivityItem[size];
+                }
+            };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return 17;
+    }
+
+    @Override
+    public String toString() {
+        return "StartActivityItem{}";
+    }
+}
+
diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java
index 63efa6f..8668bd4 100644
--- a/core/java/android/app/servertransaction/StopActivityItem.java
+++ b/core/java/android/app/servertransaction/StopActivityItem.java
@@ -31,14 +31,13 @@
 
     private static final String TAG = "StopActivityItem";
 
-    private boolean mShowWindow;
     private int mConfigChanges;
 
     @Override
     public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
-        client.handleStopActivity(token, mShowWindow, mConfigChanges, pendingActions,
+        client.handleStopActivity(token, mConfigChanges, pendingActions,
                 true /* finalStateRequest */, "STOP_ACTIVITY_ITEM");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -59,13 +58,15 @@
 
     private StopActivityItem() {}
 
-    /** Obtain an instance initialized with provided params. */
-    public static StopActivityItem obtain(boolean showWindow, int configChanges) {
+    /**
+     * Obtain an instance initialized with provided params.
+     * @param configChanges Configuration pieces that changed.
+     */
+    public static StopActivityItem obtain(int configChanges) {
         StopActivityItem instance = ObjectPool.obtain(StopActivityItem.class);
         if (instance == null) {
             instance = new StopActivityItem();
         }
-        instance.mShowWindow = showWindow;
         instance.mConfigChanges = configChanges;
 
         return instance;
@@ -74,7 +75,6 @@
     @Override
     public void recycle() {
         super.recycle();
-        mShowWindow = false;
         mConfigChanges = 0;
         ObjectPool.recycle(this);
     }
@@ -85,13 +85,11 @@
     /** Write to Parcel. */
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeBoolean(mShowWindow);
         dest.writeInt(mConfigChanges);
     }
 
     /** Read from Parcel. */
     private StopActivityItem(Parcel in) {
-        mShowWindow = in.readBoolean();
         mConfigChanges = in.readInt();
     }
 
@@ -115,20 +113,18 @@
             return false;
         }
         final StopActivityItem other = (StopActivityItem) o;
-        return mShowWindow == other.mShowWindow && mConfigChanges == other.mConfigChanges;
+        return mConfigChanges == other.mConfigChanges;
     }
 
     @Override
     public int hashCode() {
         int result = 17;
-        result = 31 * result + (mShowWindow ? 1 : 0);
         result = 31 * result + mConfigChanges;
         return result;
     }
 
     @Override
     public String toString() {
-        return "StopActivityItem{showWindow=" + mShowWindow + ",configChanges=" + mConfigChanges
-                + "}";
+        return "StopActivityItem{configChanges=" + mConfigChanges + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 20e0da3..17fcda5 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -218,7 +218,7 @@
                             null /* customIntent */);
                     break;
                 case ON_START:
-                    mTransactionHandler.handleStartActivity(r, mPendingActions);
+                    mTransactionHandler.handleStartActivity(r.token, mPendingActions);
                     break;
                 case ON_RESUME:
                     mTransactionHandler.handleResumeActivity(r.token, false /* finalStateRequest */,
@@ -230,8 +230,8 @@
                             "LIFECYCLER_PAUSE_ACTIVITY");
                     break;
                 case ON_STOP:
-                    mTransactionHandler.handleStopActivity(r.token, false /* show */,
-                            0 /* configChanges */, mPendingActions, false /* finalStateRequest */,
+                    mTransactionHandler.handleStopActivity(r.token, 0 /* configChanges */,
+                            mPendingActions, false /* finalStateRequest */,
                             "LIFECYCLER_STOP_ACTIVITY");
                     break;
                 case ON_DESTROY:
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index 0ea8c3c..6df92a7 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -183,8 +183,7 @@
                 lifecycleItem = PauseActivityItem.obtain();
                 break;
             case ON_STOP:
-                lifecycleItem = StopActivityItem.obtain(r.isVisibleFromServer(),
-                        0 /* configChanges */);
+                lifecycleItem = StopActivityItem.obtain(0 /* configChanges */);
                 break;
             default:
                 lifecycleItem = ResumeActivityItem.obtain(false /* isForward */);
diff --git a/core/java/android/app/servertransaction/WindowVisibilityItem.java b/core/java/android/app/servertransaction/WindowVisibilityItem.java
deleted file mode 100644
index 115d1ec..0000000
--- a/core/java/android/app/servertransaction/WindowVisibilityItem.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.servertransaction;
-
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
-
-import android.app.ClientTransactionHandler;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Trace;
-
-/**
- * Window visibility change message.
- * @hide
- */
-public class WindowVisibilityItem extends ClientTransactionItem {
-
-    private boolean mShowWindow;
-
-    @Override
-    public void execute(ClientTransactionHandler client, IBinder token,
-            PendingTransactionActions pendingActions) {
-        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
-                mShowWindow ? "activityShowWindow" : "activityHideWindow");
-        client.handleWindowVisibility(token, mShowWindow);
-        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
-    }
-
-
-    // ObjectPoolItem implementation
-
-    private WindowVisibilityItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    public static WindowVisibilityItem obtain(boolean showWindow) {
-        WindowVisibilityItem instance = ObjectPool.obtain(WindowVisibilityItem.class);
-        if (instance == null) {
-            instance = new WindowVisibilityItem();
-        }
-        instance.mShowWindow = showWindow;
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        mShowWindow = false;
-        ObjectPool.recycle(this);
-    }
-
-
-    // Parcelable implementation
-
-    /** Write to Parcel. */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeBoolean(mShowWindow);
-    }
-
-    /** Read from Parcel. */
-    private WindowVisibilityItem(Parcel in) {
-        mShowWindow = in.readBoolean();
-    }
-
-    public static final @android.annotation.NonNull Creator<WindowVisibilityItem> CREATOR =
-            new Creator<WindowVisibilityItem>() {
-        public WindowVisibilityItem createFromParcel(Parcel in) {
-            return new WindowVisibilityItem(in);
-        }
-
-        public WindowVisibilityItem[] newArray(int size) {
-            return new WindowVisibilityItem[size];
-        }
-    };
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        final WindowVisibilityItem other = (WindowVisibilityItem) o;
-        return mShowWindow == other.mShowWindow;
-    }
-
-    @Override
-    public int hashCode() {
-        return 17 + 31 * (mShowWindow ? 1 : 0);
-    }
-
-    @Override
-    public String toString() {
-        return "WindowVisibilityItem{showWindow=" + mShowWindow + "}";
-    }
-}
diff --git a/core/java/android/app/timedetector/ManualTimeSuggestion.java b/core/java/android/app/timedetector/ManualTimeSuggestion.java
index 55f92be..50de7385 100644
--- a/core/java/android/app/timedetector/ManualTimeSuggestion.java
+++ b/core/java/android/app/timedetector/ManualTimeSuggestion.java
@@ -20,7 +20,7 @@
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/core/java/android/app/timedetector/NetworkTimeSuggestion.java b/core/java/android/app/timedetector/NetworkTimeSuggestion.java
index 4c55ba1..17e9c5a 100644
--- a/core/java/android/app/timedetector/NetworkTimeSuggestion.java
+++ b/core/java/android/app/timedetector/NetworkTimeSuggestion.java
@@ -20,7 +20,7 @@
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
index 4a89a12..479e4b4 100644
--- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java
+++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
@@ -20,7 +20,7 @@
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index af9ece0..54dd1be 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -24,8 +24,8 @@
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.SystemClock;
+import android.os.TimestampedValue;
 import android.util.Log;
-import android.util.TimestampedValue;
 
 /**
  * The interface through which system components can send signals to the TimeDetectorService.
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 4346d97..9c4a8f4 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -16,7 +16,10 @@
 
 package android.app.usage;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.app.usage.NetworkStats.Bucket;
@@ -27,6 +30,9 @@
 import android.net.INetworkStatsService;
 import android.net.NetworkIdentity;
 import android.net.NetworkTemplate;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
+import android.net.netstats.provider.NetworkStatsProviderWrapper;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
@@ -519,6 +525,34 @@
         private DataUsageRequest request;
     }
 
+    /**
+     * Registers a custom provider of {@link android.net.NetworkStats} to combine the network
+     * statistics that cannot be seen by the kernel to system. To unregister, invoke
+     * {@link NetworkStatsProviderCallback#unregister()}.
+     *
+     * @param tag a human readable identifier of the custom network stats provider.
+     * @param provider a custom implementation of {@link AbstractNetworkStatsProvider} that needs to
+     *                 be registered to the system.
+     * @return a {@link NetworkStatsProviderCallback}, which can be used to report events to the
+     *         system.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+    @NonNull public NetworkStatsProviderCallback registerNetworkStatsProvider(
+            @NonNull String tag,
+            @NonNull AbstractNetworkStatsProvider provider) {
+        try {
+            final NetworkStatsProviderWrapper wrapper = new NetworkStatsProviderWrapper(provider);
+            return new NetworkStatsProviderCallback(
+                    mService.registerNetworkStatsProvider(tag, wrapper));
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+        // Unreachable code, but compiler doesn't know about it.
+        return null;
+    }
+
     private static NetworkTemplate createTemplate(int networkType, String subscriberId) {
         final NetworkTemplate template;
         switch (networkType) {
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index b1b6f0d..cb1f055 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -2670,6 +2670,9 @@
         } else if (profile == BluetoothProfile.PAN) {
             BluetoothPan pan = new BluetoothPan(context, listener);
             return true;
+        } else if (profile == BluetoothProfile.PBAP) {
+            BluetoothPbap pbap = new BluetoothPbap(context, listener);
+            return true;
         } else if (profile == BluetoothProfile.HEALTH) {
             Log.e(TAG, "getProfileProxy(): BluetoothHealth is deprecated");
             return false;
@@ -2742,6 +2745,10 @@
                 BluetoothPan pan = (BluetoothPan) proxy;
                 pan.close();
                 break;
+            case BluetoothProfile.PBAP:
+                BluetoothPbap pbap = (BluetoothPbap) proxy;
+                pbap.close();
+                break;
             case BluetoothProfile.GATT:
                 BluetoothGatt gatt = (BluetoothGatt) proxy;
                 gatt.close();
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index e9b0be2..a923be6 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -16,8 +16,12 @@
 
 package android.bluetooth;
 
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.Binder;
 import android.os.IBinder;
@@ -36,6 +40,7 @@
  */
 public final class BluetoothHidDevice implements BluetoothProfile {
     private static final String TAG = BluetoothHidDevice.class.getSimpleName();
+    private static final boolean DBG = false;
 
     /**
      * Intent used to broadcast the change in connection state of the Input Host profile.
@@ -682,4 +687,62 @@
 
         return result;
     }
+
+    /**
+     * Connects Hid Device if connectionPolicy is {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}
+     * and disconnects Hid device if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}.
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy determines whether hid device should be connected or disconnected
+     * @return true if hid device is connected or disconnected, false otherwise
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        try {
+            final IBluetoothHidDevice service = getService();
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                    return false;
+                }
+                return service.setConnectionPolicy(device, connectionPolicy);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    private boolean isEnabled() {
+        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+        return false;
+    }
+
+    private boolean isValidDevice(BluetoothDevice device) {
+        if (device == null) return false;
+
+        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+        return false;
+    }
+
+    private static void log(String msg) {
+        if (DBG) {
+            Log.d(TAG, msg);
+        }
+    }
 }
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index 917e7fa..4674706 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -17,7 +17,10 @@
 package android.bluetooth;
 
 import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -35,21 +38,35 @@
  *
  * @hide
  */
+@SystemApi
 public final class BluetoothMap implements BluetoothProfile {
 
     private static final String TAG = "BluetoothMap";
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
+    /** @hide */
+    @SuppressLint("ActionValue")
+    @SystemApi
     public static final String ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
 
-    /** There was an error trying to obtain the state */
+    /**
+     * There was an error trying to obtain the state
+     *
+     * @hide
+     */
     public static final int STATE_ERROR = -1;
 
+    /** @hide */
     public static final int RESULT_FAILURE = 0;
+    /** @hide */
     public static final int RESULT_SUCCESS = 1;
-    /** Connection canceled before completion. */
+    /**
+     * Connection canceled before completion.
+     *
+     * @hide
+     */
     public static final int RESULT_CANCELED = 2;
 
     private BluetoothAdapter mAdapter;
@@ -71,6 +88,7 @@
         mProfileConnector.connect(context, listener);
     }
 
+    @SuppressLint("GenericException")
     protected void finalize() throws Throwable {
         try {
             close();
@@ -84,6 +102,8 @@
      * Other public functions of BluetoothMap will return default error
      * results once close() has been called. Multiple invocations of close()
      * are ok.
+     *
+     * @hide
      */
     public synchronized void close() {
         mProfileConnector.disconnect();
@@ -98,6 +118,8 @@
      *
      * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not
      * connected to the Map service.
+     *
+     * @hide
      */
     public int getState() {
         if (VDBG) log("getState()");
@@ -120,6 +142,8 @@
      *
      * @return The remote Bluetooth device, or null if not in connected or connecting state, or if
      * this proxy object is not connected to the Map service.
+     *
+     * @hide
      */
     public BluetoothDevice getClient() {
         if (VDBG) log("getClient()");
@@ -141,6 +165,8 @@
      * Returns true if the specified Bluetooth device is connected.
      * Returns false if not connected, or if this proxy object is not
      * currently connected to the Map service.
+     *
+     * @hide
      */
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) log("isConnected(" + device + ")");
@@ -161,6 +187,8 @@
     /**
      * Initiate connection. Initiation of outgoing connections is not
      * supported for MAP server.
+     *
+     * @hide
      */
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")" + "not supported for MAPS");
@@ -172,6 +200,8 @@
      *
      * @param device Remote Bluetooth Device
      * @return false on error, true otherwise
+     *
+     * @hide
      */
     @UnsupportedAppUsage
     public boolean disconnect(BluetoothDevice device) {
@@ -196,6 +226,8 @@
      * devices. It tries to err on the side of false positives.
      *
      * @return True if this device might support Map.
+     *
+     * @hide
      */
     public static boolean doesClassMatchSink(BluetoothClass btClass) {
         // TODO optimize the rule
@@ -214,8 +246,11 @@
      * Get the list of connected devices. Currently at most one.
      *
      * @return list of connected devices
+     *
+     * @hide
      */
-    public List<BluetoothDevice> getConnectedDevices() {
+    @SystemApi
+    public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (DBG) log("getConnectedDevices()");
         final IBluetoothMap service = getService();
         if (service != null && isEnabled()) {
@@ -234,6 +269,8 @@
      * Get the list of devices matching specified states. Currently at most one.
      *
      * @return list of matching devices
+     *
+     * @hide
      */
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) log("getDevicesMatchingStates()");
@@ -254,6 +291,8 @@
      * Get connection state of device
      *
      * @return device connection state
+     *
+     * @hide
      */
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) log("getConnectionState(" + device + ")");
@@ -301,7 +340,7 @@
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
-    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+    public boolean setConnectionPolicy(@Nullable BluetoothDevice device, int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothMap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
@@ -349,7 +388,7 @@
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH)
-    public int getConnectionPolicy(BluetoothDevice device) {
+    public int getConnectionPolicy(@Nullable BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothMap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index 42f27f2..024bb06 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -16,9 +16,11 @@
 
 package android.bluetooth;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SuppressLint;
@@ -256,6 +258,41 @@
     }
 
     /**
+     * Set connection policy of the profile
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+     * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        try {
+            final IBluetoothPan service = getService();
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                    return false;
+                }
+                return service.setConnectionPolicy(device, connectionPolicy);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    /**
      * {@inheritDoc}
      */
     @Override
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index 948885e..e07ca52 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -274,15 +274,15 @@
     }
 
     /**
-     * Pbap does not store connection policy, so this function only disconnects Pbap if
-     * connectionPolicy is CONNECTION_POLICY_FORBIDDEN.
+     * Pbap does not store connection policy, so this function only disconnects pbap if
+     * connectionPolicy is {@link #CONNECTION_POLICY_FORBIDDEN}.
      *
      * <p> The device should already be paired.
      * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
      * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
      *
      * @param device Paired bluetooth device
-     * @param connectionPolicy is the connection policy to set to for this profile
+     * @param connectionPolicy determines whether to disconnect the device
      * @return true if pbap is successfully disconnected, false otherwise
      * @hide
      */
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 393d488..85826fd 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -27,6 +27,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.app.AppOpsManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.PackageManager;
@@ -942,7 +943,18 @@
         return null;
     }
 
-    /** {@hide} */
+    /**
+     * Return the package name of the caller that initiated the request being
+     * processed on the current thread. The returned package will have
+     * <em>not</em> been verified to belong to the calling UID. Returns
+     * {@code null} if not currently processing a request.
+     * <p>
+     * This will always return {@code null} when processing
+     * {@link #getType(Uri)} or {@link #getStreamTypes(Uri, String)} requests.
+     *
+     * @see Binder#getCallingUid()
+     * @see Context#grantUriPermission(String, Uri, int)
+     */
     public final @Nullable String getCallingPackageUnchecked() {
         final Pair<String, String> pkg = mCallingPackage.get();
         if (pkg != null) {
@@ -952,7 +964,14 @@
         return null;
     }
 
-    /** {@hide} */
+    /**
+     * Called whenever the value of {@link #getCallingPackage()} changes, giving
+     * the provider an opportunity to invalidate any security related caching it
+     * may be performing.
+     * <p>
+     * This typically happens when a {@link ContentProvider} makes a nested call
+     * back into itself when already processing a call from a remote process.
+     */
     public void onCallingPackageChanged() {
     }
 
@@ -1390,8 +1409,11 @@
      * @param uri The URI to query. This will be the full URI sent by the client.
      * @param projection The list of columns to put into the cursor.
      *            If {@code null} provide a default set of columns.
-     * @param queryArgs A Bundle containing all additional information necessary for the query.
-     *            Values in the Bundle may include SQL style arguments.
+     * @param queryArgs A Bundle containing additional information necessary for
+     *            the operation. Arguments may include SQL style arguments, such
+     *            as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+     *            the documentation for each individual provider will indicate
+     *            which arguments they support.
      * @param cancellationSignal A signal to cancel the operation in progress,
      *            or {@code null}.
      * @return a Cursor or {@code null}.
@@ -1525,8 +1547,24 @@
         return false;
     }
 
-    /** {@hide} */
+    /**
+     * Perform a detailed internal check on a {@link Uri} to determine if a UID
+     * is able to access it with specific mode flags.
+     * <p>
+     * This method is typically used when the provider implements more dynamic
+     * access controls that cannot be expressed with {@code <path-permission>}
+     * style static rules.
+     *
+     * @param uri the {@link Uri} to perform an access check on.
+     * @param uid the UID to check the permission for.
+     * @param modeFlags the access flags to use for the access check, such as
+     *            {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}.
+     * @return {@link PackageManager#PERMISSION_GRANTED} if access is allowed,
+     *         otherwise {@link PackageManager#PERMISSION_DENIED}.
+     * @hide
+     */
     @Override
+    @SystemApi
     public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags) {
         return PackageManager.PERMISSION_DENIED;
     }
@@ -1574,9 +1612,14 @@
      *
      * @param uri The content:// URI of the insertion request.
      * @param values A set of column_name/value pairs to add to the database.
-     * @param extras A Bundle containing all additional information necessary
-     *            for the insert.
+     * @param extras A Bundle containing additional information necessary for
+     *            the operation. Arguments may include SQL style arguments, such
+     *            as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+     *            the documentation for each individual provider will indicate
+     *            which arguments they support.
      * @return The URI for the newly inserted item.
+     * @throws IllegalArgumentException if the provider doesn't support one of
+     *             the requested Bundle arguments.
      */
     @Override
     public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues values,
@@ -1653,10 +1696,13 @@
      *
      * @param uri The full URI to query, including a row ID (if a specific
      *            record is requested).
-     * @param extras A Bundle containing all additional information necessary
-     *            for the delete. Values in the Bundle may include SQL style
-     *            arguments.
-     * @return The number of rows affected.
+     * @param extras A Bundle containing additional information necessary for
+     *            the operation. Arguments may include SQL style arguments, such
+     *            as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+     *            the documentation for each individual provider will indicate
+     *            which arguments they support.
+     * @throws IllegalArgumentException if the provider doesn't support one of
+     *             the requested Bundle arguments.
      * @throws SQLException
      */
     @Override
@@ -1699,10 +1745,14 @@
      * @param uri The URI to query. This can potentially have a record ID if
      *            this is an update request for a specific record.
      * @param values A set of column_name/value pairs to update in the database.
-     * @param extras A Bundle containing all additional information necessary
-     *            for the update. Values in the Bundle may include SQL style
-     *            arguments.
+     * @param extras A Bundle containing additional information necessary for
+     *            the operation. Arguments may include SQL style arguments, such
+     *            as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+     *            the documentation for each individual provider will indicate
+     *            which arguments they support.
      * @return the number of rows affected.
+     * @throws IllegalArgumentException if the provider doesn't support one of
+     *             the requested Bundle arguments.
      */
     @Override
     public int update(@NonNull Uri uri, @Nullable ContentValues values,
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index 93f4287..494d2ae 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
diff --git a/core/java/android/content/ContentProviderResult.java b/core/java/android/content/ContentProviderResult.java
index 11dda83..4fb1ddb 100644
--- a/core/java/android/content/ContentProviderResult.java
+++ b/core/java/android/content/ContentProviderResult.java
@@ -36,7 +36,7 @@
     public final @Nullable Uri uri;
     public final @Nullable Integer count;
     public final @Nullable Bundle extras;
-    public final @Nullable Exception exception;
+    public final @Nullable Throwable exception;
 
     public ContentProviderResult(@NonNull Uri uri) {
         this(Objects.requireNonNull(uri), null, null, null);
@@ -50,12 +50,12 @@
         this(null, null, Objects.requireNonNull(extras), null);
     }
 
-    public ContentProviderResult(@NonNull Exception exception) {
+    public ContentProviderResult(@NonNull Throwable exception) {
         this(null, null, null, exception);
     }
 
     /** {@hide} */
-    public ContentProviderResult(Uri uri, Integer count, Bundle extras, Exception exception) {
+    public ContentProviderResult(Uri uri, Integer count, Bundle extras, Throwable exception) {
         this.uri = uri;
         this.count = count;
         this.extras = extras;
@@ -79,7 +79,7 @@
             extras = null;
         }
         if (source.readInt() != 0) {
-            exception = (Exception) ParcelableException.readFromParcel(source);
+            exception = ParcelableException.readFromParcel(source);
         } else {
             exception = null;
         }
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 1d3c650..6cd1cd3 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -984,7 +984,11 @@
      *         retrieve.
      * @param projection A list of which columns to return. Passing null will
      *         return all columns, which is inefficient.
-     * @param queryArgs A Bundle containing any arguments to the query.
+     * @param queryArgs A Bundle containing additional information necessary for
+     *            the operation. Arguments may include SQL style arguments, such
+     *            as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+     *            the documentation for each individual provider will indicate
+     *            which arguments they support.
      * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
      * If the operation is canceled, then {@link OperationCanceledException} will be thrown
      * when the query is executed.
@@ -1925,9 +1929,15 @@
      * @param url The URL of the table to insert into.
      * @param values The initial values for the newly inserted row. The key is the column name for
      *               the field. Passing an empty ContentValues will create an empty row.
-     * @param extras A Bundle containing all additional information necessary for the insert.
+     * @param extras A Bundle containing additional information necessary for
+     *            the operation. Arguments may include SQL style arguments, such
+     *            as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+     *            the documentation for each individual provider will indicate
+     *            which arguments they support.
      * @return the URL of the newly created row. May return <code>null</code> if the underlying
      *         content provider returns <code>null</code>, or if it crashes.
+     * @throws IllegalArgumentException if the provider doesn't support one of
+     *             the requested Bundle arguments.
      */
     @Override
     public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url,
@@ -2061,9 +2071,14 @@
      * If the content provider supports transactions, the deletion will be atomic.
      *
      * @param url The URL of the row to delete.
-     * @param extras A Bundle containing all additional information necessary for the delete.
-     *            Values in the Bundle may include SQL style arguments.
+     * @param extras A Bundle containing additional information necessary for
+     *            the operation. Arguments may include SQL style arguments, such
+     *            as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+     *            the documentation for each individual provider will indicate
+     *            which arguments they support.
      * @return The number of rows deleted.
+     * @throws IllegalArgumentException if the provider doesn't support one of
+     *             the requested Bundle arguments.
      */
     @Override
     public final int delete(@RequiresPermission.Write @NonNull Uri url, @Nullable Bundle extras) {
@@ -2121,10 +2136,15 @@
      * @param uri The URI to modify.
      * @param values The new field values. The key is the column name for the field.
                      A null value will remove an existing field value.
-     * @param extras A Bundle containing all additional information necessary for the update.
-     *            Values in the Bundle may include SQL style arguments.
+     * @param extras A Bundle containing additional information necessary for
+     *            the operation. Arguments may include SQL style arguments, such
+     *            as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+     *            the documentation for each individual provider will indicate
+     *            which arguments they support.
      * @return the number of rows updated.
      * @throws NullPointerException if uri or values are null
+     * @throws IllegalArgumentException if the provider doesn't support one of
+     *             the requested Bundle arguments.
      */
     @Override
     public final int update(@RequiresPermission.Write @NonNull Uri uri,
@@ -3851,15 +3871,47 @@
         }
     }
 
+    /**
+     * Decode a path generated by {@link #encodeToFile(Uri)} back into
+     * the original {@link Uri}.
+     * <p>
+     * This is used to offer a way to intercept filesystem calls in
+     * {@link ContentProvider} unaware code and redirect them to a
+     * {@link ContentProvider} when they attempt to use {@code _DATA} columns
+     * that are otherwise deprecated.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static @NonNull Uri decodeFromFile(@NonNull File file) {
+        return translateDeprecatedDataPath(file.getAbsolutePath());
+    }
+
+    /**
+     * Encode a {@link Uri} into an opaque filesystem path which can then be
+     * resurrected by {@link #decodeFromFile(File)}.
+     * <p>
+     * This is used to offer a way to intercept filesystem calls in
+     * {@link ContentProvider} unaware code and redirect them to a
+     * {@link ContentProvider} when they attempt to use {@code _DATA} columns
+     * that are otherwise deprecated.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static @NonNull File encodeToFile(@NonNull Uri uri) {
+        return new File(translateDeprecatedDataPath(uri));
+    }
+
     /** {@hide} */
-    public static Uri translateDeprecatedDataPath(String path) {
+    public static @NonNull Uri translateDeprecatedDataPath(@NonNull String path) {
         final String ssp = "//" + path.substring(DEPRECATE_DATA_PREFIX.length());
         return Uri.parse(new Uri.Builder().scheme(SCHEME_CONTENT)
                 .encodedOpaquePart(ssp).build().toString());
     }
 
     /** {@hide} */
-    public static String translateDeprecatedDataPath(Uri uri) {
+    public static @NonNull String translateDeprecatedDataPath(@NonNull Uri uri) {
         return DEPRECATE_DATA_PREFIX + uri.getEncodedSchemeSpecificPart().substring(2);
     }
 }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a28868e..1b40a18 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3240,15 +3240,40 @@
     }
 
     /**
-     * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle
-     * argument for use by system server and other multi-user aware code.
-     * @hide
+     * Binds to a service in the given {@code user} in the same manner as
+     * {@link #bindService(Intent, ServiceConnection, int)}.
+     *
+     * <p>If the given {@code user} is in the same profile group and the target package is the
+     * same as the caller, {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} is
+     * sufficient. Otherwise, requires {@code android.Manifest.permission.INTERACT_ACROSS_USERS}
+     * for interacting with other users.
+     *
+     * @param service Identifies the service to connect to.  The Intent must
+     *      specify an explicit component name.
+     * @param conn Receives information as the service is started and stopped.
+     *      This must be a valid ServiceConnection object; it must not be null.
+     * @param flags Operation options for the binding.  May be 0,
+     *          {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND},
+     *          {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT},
+     *          {@link #BIND_ALLOW_OOM_MANAGEMENT}, {@link #BIND_WAIVE_PRIORITY}.
+     *          {@link #BIND_IMPORTANT}, or
+     *          {@link #BIND_ADJUST_WITH_ACTIVITY}.
+     * @return {@code true} if the system is in the process of bringing up a
+     *         service that your client has permission to bind to; {@code false}
+     *         if the system couldn't find the service. If this value is {@code true}, you
+     *         should later call {@link #unbindService} to release the
+     *         connection.
+     *
+     * @throws SecurityException if the client does not have the required permission to bind.
      */
-    @SystemApi
     @SuppressWarnings("unused")
-    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
-    public boolean bindServiceAsUser(@RequiresPermission Intent service, ServiceConnection conn,
-            int flags, UserHandle user) {
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.INTERACT_ACROSS_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_PROFILES
+    })
+    public boolean bindServiceAsUser(
+            @NonNull @RequiresPermission Intent service, @NonNull ServiceConnection conn, int flags,
+            @NonNull UserHandle user) {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
     }
 
@@ -3391,6 +3416,7 @@
             TELEPHONY_SUBSCRIPTION_SERVICE,
             CARRIER_CONFIG_SERVICE,
             EUICC_SERVICE,
+            MMS_SERVICE,
             TELECOM_SERVICE,
             CLIPBOARD_SERVICE,
             INPUT_METHOD_SERVICE,
@@ -3587,6 +3613,8 @@
      * @see android.telephony.CarrierConfigManager
      * @see #EUICC_SERVICE
      * @see android.telephony.euicc.EuiccManager
+     * @see #MMS_SERVICE
+     * @see android.telephony.MmsManager
      * @see #INPUT_METHOD_SERVICE
      * @see android.view.inputmethod.InputMethodManager
      * @see #UI_MODE_SERVICE
@@ -4016,6 +4044,7 @@
      * @see android.net.wifi.WifiCondManager
      * @hide
      */
+    @SystemApi
     public static final String WIFI_COND_SERVICE = "wificond";
 
     /**
@@ -4262,6 +4291,15 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.telephony.MmsManager} to send/receive MMS messages.
+     *
+     * @see #getSystemService(String)
+     * @see android.telephony.MmsManager
+     */
+    public static final String MMS_SERVICE = "mms";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
      * {@link android.content.ClipboardManager} for accessing and modifying
      * the contents of the global clipboard.
      *
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 3bb0f92..c8f587f 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6440,19 +6440,22 @@
      */
     public static final int FLAG_RECEIVER_NO_ABORT = 0x08000000;
     /**
-     * If set, when sending a broadcast <i>before boot has completed</i> only
+     * If set, when sending a broadcast <i>before the system has fully booted up
+     * (which is even before {@link #ACTION_LOCKED_BOOT_COMPLETED} has been sent)"</i> only
      * registered receivers will be called -- no BroadcastReceiver components
      * will be launched.  Sticky intent state will be recorded properly even
      * if no receivers wind up being called.  If {@link #FLAG_RECEIVER_REGISTERED_ONLY}
      * is specified in the broadcast intent, this flag is unnecessary.
      *
-     * <p>This flag is only for use by system sevices as a convenience to
-     * avoid having to implement a more complex mechanism around detection
+     * <p>This flag is only for use by system services (even services from mainline modules) as a
+     * convenience to avoid having to implement a more complex mechanism around detection
      * of boot completion.
      *
+     * <p>This is useful to system server mainline modules
+     *
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
     public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x04000000;
     /**
      * Set when this broadcast is for a boot upgrade, a special mode that
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index e897b91..9d57514 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -16,20 +16,25 @@
 package android.content.pm;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 
 import com.android.internal.R;
 import com.android.internal.util.UserIcons;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * Class for handling cross profile operations. Apps can use this class to interact with its
@@ -169,6 +174,86 @@
         }
     }
 
+    /**
+     * Returns whether the calling package can request to interact across profiles.
+     *
+     * <p>The package's current ability to interact across profiles can be checked with
+     * {@link #canInteractAcrossProfiles()}.
+     *
+     * <p>Specifically, returns whether the following are all true:
+     * <ul>
+     * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li>
+     * <li>The calling app has requested</li>
+     * {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} in its manifest.
+     * <li>The calling package has either been whitelisted by default by the OEM or has been
+     * explicitly whitelisted by the admin via
+     * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.
+     * </li>
+     * </ul>
+     *
+     * @return true if the calling package can request to interact across profiles.
+     */
+    public boolean canRequestInteractAcrossProfiles() {
+        try {
+            return mService.canRequestInteractAcrossProfiles(mContext.getPackageName());
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether the calling package can interact across profiles.
+     *
+     * <p>The package's current ability to request to interact across profiles can be checked with
+     * {@link #canRequestInteractAcrossProfiles()}.
+     *
+     * <p>Specifically, returns whether the following are all true:
+     * <ul>
+     * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li>
+     * <li>The user has previously consented to cross-profile communication for the calling
+     * package.</li>
+     * <li>The calling package has either been whitelisted by default by the OEM or has been
+     * explicitly whitelisted by the admin via
+     * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.
+     * </li>
+     * </ul>
+     *
+     * @return true if the calling package can interact across profiles.
+     * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the
+     * calling UID.
+     */
+    public boolean canInteractAcrossProfiles() {
+        try {
+            return mService.canInteractAcrossProfiles(mContext.getPackageName());
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns an {@link Intent} to open the settings page that allows the user to decide whether
+     * the calling app can interact across profiles. The current state is given by
+     * {@link #canInteractAcrossProfiles()}.
+     *
+     * <p>Returns {@code null} if {@link #canRequestInteractAcrossProfiles()} is {@code false}.
+     *
+     * @return an {@link Intent} to open the settings page that allows the user to decide whether
+     * the app can interact across profiles
+     *
+     * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the
+     * calling UID.
+     */
+    public @Nullable Intent createRequestInteractAcrossProfilesIntent() {
+        if (!canRequestInteractAcrossProfiles()) {
+            return null;
+        }
+        final Intent settingsIntent = new Intent();
+        settingsIntent.setAction(Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS);
+        final Uri packageUri = Uri.parse("package:" + mContext.getPackageName());
+        settingsIntent.setData(packageUri);
+        return settingsIntent;
+    }
+
     private void verifyCanAccessUser(UserHandle userHandle) {
         if (!getTargetUserProfiles().contains(userHandle)) {
             throw new SecurityException("Not allowed to access " + userHandle);
diff --git a/core/java/android/content/pm/ICrossProfileApps.aidl b/core/java/android/content/pm/ICrossProfileApps.aidl
index d2d66cb..c5db0cc 100644
--- a/core/java/android/content/pm/ICrossProfileApps.aidl
+++ b/core/java/android/content/pm/ICrossProfileApps.aidl
@@ -30,4 +30,6 @@
     void startActivityAsUser(in IApplicationThread caller, in String callingPackage,
             in ComponentName component, int userId, boolean launchMainActivity);
     List<UserHandle> getTargetUserProfiles(in String callingPackage);
+    boolean canInteractAcrossProfiles(in String callingPackage);
+    boolean canRequestInteractAcrossProfiles(in String callingPackage);
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b85c58a..4bfc40e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2323,6 +2323,13 @@
     public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+     * has a telephony radio that support data.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_TELEPHONY_DATA = "android.hardware.telephony.data";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
      * The device supports telephony carrier restriction mechanism.
      *
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 32803ab..87acbc1 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -7803,7 +7803,7 @@
             ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
         }
         ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
-        ai.resourceDirs = state.overlayPaths;
+        ai.resourceDirs = state.getAllOverlayPaths();
         ai.icon = (sUseRoundIcon && ai.roundIconRes != 0) ? ai.roundIconRes : ai.iconRes;
     }
 
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index f0f6753..da7623a 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -46,6 +46,8 @@
 
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -77,7 +79,9 @@
     public ArraySet<String> disabledComponents;
     public ArraySet<String> enabledComponents;
 
-    public String[] overlayPaths;
+    private String[] overlayPaths;
+    private ArrayMap<String, String[]> sharedLibraryOverlayPaths; // Lib name to overlay paths
+    private String[] cachedOverlayPaths;
 
     @UnsupportedAppUsage
     public PackageUserState() {
@@ -112,9 +116,33 @@
         enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
         overlayPaths =
             o.overlayPaths == null ? null : Arrays.copyOf(o.overlayPaths, o.overlayPaths.length);
+        if (o.sharedLibraryOverlayPaths != null) {
+            sharedLibraryOverlayPaths = new ArrayMap<>(o.sharedLibraryOverlayPaths);
+        }
         harmfulAppWarning = o.harmfulAppWarning;
     }
 
+    public String[] getOverlayPaths() {
+        return overlayPaths;
+    }
+
+    public void setOverlayPaths(String[] paths) {
+        overlayPaths = paths;
+        cachedOverlayPaths = null;
+    }
+
+    public Map<String, String[]> getSharedLibraryOverlayPaths() {
+        return sharedLibraryOverlayPaths;
+    }
+
+    public void setSharedLibraryOverlayPaths(String library, String[] paths) {
+        if (sharedLibraryOverlayPaths == null) {
+            sharedLibraryOverlayPaths = new ArrayMap<>();
+        }
+        sharedLibraryOverlayPaths.put(library, paths);
+        cachedOverlayPaths = null;
+    }
+
     /**
      * Test if this package is installed.
      */
@@ -235,6 +263,38 @@
         return isComponentEnabled;
     }
 
+    public String[] getAllOverlayPaths() {
+        if (overlayPaths == null && sharedLibraryOverlayPaths == null) {
+            return null;
+        }
+
+        if (cachedOverlayPaths != null) {
+            return cachedOverlayPaths;
+        }
+
+        final LinkedHashSet<String> paths = new LinkedHashSet<>();
+        if (overlayPaths != null) {
+            final int N = overlayPaths.length;
+            for (int i = 0; i < N; i++) {
+                paths.add(overlayPaths[i]);
+            }
+        }
+
+        if (sharedLibraryOverlayPaths != null) {
+            for (String[] libOverlayPaths : sharedLibraryOverlayPaths.values()) {
+                if (libOverlayPaths != null) {
+                    final int N = libOverlayPaths.length;
+                    for (int i = 0; i < N; i++) {
+                        paths.add(libOverlayPaths[i]);
+                    }
+                }
+            }
+        }
+
+        cachedOverlayPaths = paths.toArray(new String[0]);
+        return cachedOverlayPaths;
+    }
+
     @Override
     final public boolean equals(Object obj) {
         if (!(obj instanceof PackageUserState)) {
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index ac2e373..6639b3d 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -18,7 +18,7 @@
 
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
diff --git a/core/java/android/content/pm/parsing/ComponentParseUtils.java b/core/java/android/content/pm/parsing/ComponentParseUtils.java
index 5956857..f04a30c 100644
--- a/core/java/android/content/pm/parsing/ComponentParseUtils.java
+++ b/core/java/android/content/pm/parsing/ComponentParseUtils.java
@@ -27,8 +27,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringRes;
-import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityTaskManager;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
diff --git a/core/java/android/content/pm/parsing/PackageInfoUtils.java b/core/java/android/content/pm/parsing/PackageInfoUtils.java
index f2cf9a4..73a8d2a 100644
--- a/core/java/android/content/pm/parsing/PackageInfoUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoUtils.java
@@ -41,9 +41,11 @@
 import android.content.pm.parsing.ComponentParseUtils.ParsedInstrumentation;
 import android.content.pm.parsing.ComponentParseUtils.ParsedPermission;
 import android.content.pm.parsing.ComponentParseUtils.ParsedPermissionGroup;
+import android.util.ArraySet;
 
 import com.android.internal.util.ArrayUtils;
 
+import java.util.LinkedHashSet;
 import java.util.Set;
 
 /** @hide */
@@ -545,7 +547,7 @@
             ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
         }
         ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
-        ai.resourceDirs = state.overlayPaths;
+        ai.resourceDirs = state.getAllOverlayPaths();
         ai.icon = (PackageParser.sUseRoundIcon && ai.roundIconRes != 0)
                 ? ai.roundIconRes : ai.iconRes;
     }
diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING
new file mode 100644
index 0000000..daf9a14
--- /dev/null
+++ b/core/java/android/content/res/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksResourceLoaderTests"
+    }
+  ]
+}
diff --git a/core/java/android/content/rollback/PackageRollbackInfo.java b/core/java/android/content/rollback/PackageRollbackInfo.java
index 6378db0..b273cd6 100644
--- a/core/java/android/content/rollback/PackageRollbackInfo.java
+++ b/core/java/android/content/rollback/PackageRollbackInfo.java
@@ -76,6 +76,11 @@
      */
     private final boolean mIsApex;
 
+    /**
+     * Whether this instance represents the PackageRollbackInfo for an APK in APEX.
+     */
+    private final boolean mIsApkInApex;
+
     /*
      * The list of users for which snapshots have been saved.
      */
@@ -157,6 +162,10 @@
     public @PackageManager.RollbackDataPolicy int getRollbackDataPolicy() {
         return mRollbackDataPolicy;
     }
+    /** @hide */
+    public boolean isApkInApex() {
+        return mIsApkInApex;
+    }
 
     /** @hide */
     public IntArray getSnapshottedUsers() {
@@ -190,17 +199,18 @@
     public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
             VersionedPackage packageRolledBackTo,
             @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores,
-            boolean isApex, @NonNull IntArray snapshottedUsers,
+            boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers,
             @NonNull SparseLongArray ceSnapshotInodes) {
         this(packageRolledBackFrom, packageRolledBackTo, pendingBackups, pendingRestores, isApex,
-                snapshottedUsers, ceSnapshotInodes, PackageManager.RollbackDataPolicy.RESTORE);
+                isApkInApex, snapshottedUsers, ceSnapshotInodes,
+                PackageManager.RollbackDataPolicy.RESTORE);
     }
 
     /** @hide */
     public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
             VersionedPackage packageRolledBackTo,
             @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores,
-            boolean isApex, @NonNull IntArray snapshottedUsers,
+            boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers,
             @NonNull SparseLongArray ceSnapshotInodes,
             @PackageManager.RollbackDataPolicy int rollbackDataPolicy) {
         this.mVersionRolledBackFrom = packageRolledBackFrom;
@@ -209,6 +219,7 @@
         this.mPendingRestores = pendingRestores;
         this.mIsApex = isApex;
         this.mRollbackDataPolicy = rollbackDataPolicy;
+        this.mIsApkInApex = isApkInApex;
         this.mSnapshottedUsers = snapshottedUsers;
         this.mCeSnapshotInodes = ceSnapshotInodes;
     }
@@ -217,6 +228,7 @@
         this.mVersionRolledBackFrom = VersionedPackage.CREATOR.createFromParcel(in);
         this.mVersionRolledBackTo = VersionedPackage.CREATOR.createFromParcel(in);
         this.mIsApex = in.readBoolean();
+        this.mIsApkInApex = in.readBoolean();
         this.mPendingRestores = null;
         this.mPendingBackups = null;
         this.mSnapshottedUsers = null;
@@ -234,6 +246,7 @@
         mVersionRolledBackFrom.writeToParcel(out, flags);
         mVersionRolledBackTo.writeToParcel(out, flags);
         out.writeBoolean(mIsApex);
+        out.writeBoolean(mIsApkInApex);
     }
 
     public static final @NonNull Parcelable.Creator<PackageRollbackInfo> CREATOR =
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index bba14c3..36ec67e 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -35,8 +35,8 @@
 import libcore.util.EmptyArray;
 
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -56,7 +56,7 @@
             "(?i)(AVG|COUNT|MAX|MIN|SUM|TOTAL|GROUP_CONCAT)\\((.+)\\)");
 
     private Map<String, String> mProjectionMap = null;
-    private List<Pattern> mProjectionGreylist = null;
+    private Collection<Pattern> mProjectionGreylist = null;
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private String mTables = "";
@@ -196,20 +196,16 @@
      * Sets a projection greylist of columns that will be allowed through, even
      * when {@link #setStrict(boolean)} is enabled. This provides a way for
      * abusive custom columns like {@code COUNT(*)} to continue working.
-     *
-     * @hide
      */
-    public void setProjectionGreylist(@Nullable List<Pattern> projectionGreylist) {
+    public void setProjectionGreylist(@Nullable Collection<Pattern> projectionGreylist) {
         mProjectionGreylist = projectionGreylist;
     }
 
     /**
      * Gets the projection greylist for the query, as last configured by
-     * {@link #setProjectionGreylist(List)}.
-     *
-     * @hide
+     * {@link #setProjectionGreylist}.
      */
-    public @Nullable List<Pattern> getProjectionGreylist() {
+    public @Nullable Collection<Pattern> getProjectionGreylist() {
         return mProjectionGreylist;
     }
 
@@ -244,25 +240,27 @@
     }
 
     /**
-     * When set, the selection is verified against malicious arguments.
-     * When using this class to create a statement using
+     * When set, the selection is verified against malicious arguments. When
+     * using this class to create a statement using
      * {@link #buildQueryString(boolean, String, String[], String, String, String, String, String)},
-     * non-numeric limits will raise an exception. If a projection map is specified, fields
-     * not in that map will be ignored.
-     * If this class is used to execute the statement directly using
+     * non-numeric limits will raise an exception. If a projection map is
+     * specified, fields not in that map will be ignored. If this class is used
+     * to execute the statement directly using
      * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String)}
      * or
      * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String, String)},
-     * additionally also parenthesis escaping selection are caught.
-     *
-     * To summarize: To get maximum protection against malicious third party apps (for example
-     * content provider consumers), make sure to do the following:
+     * additionally also parenthesis escaping selection are caught. To
+     * summarize: To get maximum protection against malicious third party apps
+     * (for example content provider consumers), make sure to do the following:
      * <ul>
      * <li>Set this value to true</li>
      * <li>Use a projection map</li>
-     * <li>Use one of the query overloads instead of getting the statement as a sql string</li>
+     * <li>Use one of the query overloads instead of getting the statement as a
+     * sql string</li>
      * </ul>
-     * By default, this value is false.
+     * <p>
+     * This feature is disabled by default on each newly constructed
+     * {@link SQLiteQueryBuilder} and needs to be manually enabled.
      */
     public void setStrict(boolean strict) {
         if (strict) {
@@ -287,6 +285,9 @@
      * This enforcement applies to {@link #insert}, {@link #query}, and
      * {@link #update} operations. Any enforcement failures will throw an
      * {@link IllegalArgumentException}.
+     * <p>
+     * This feature is disabled by default on each newly constructed
+     * {@link SQLiteQueryBuilder} and needs to be manually enabled.
      */
     public void setStrictColumns(boolean strictColumns) {
         if (strictColumns) {
@@ -323,6 +324,9 @@
      * {@link #delete} operations. This enforcement does not apply to trusted
      * inputs, such as those provided by {@link #appendWhere}. Any enforcement
      * failures will throw an {@link IllegalArgumentException}.
+     * <p>
+     * This feature is disabled by default on each newly constructed
+     * {@link SQLiteQueryBuilder} and needs to be manually enabled.
      */
     public void setStrictGrammar(boolean strictGrammar) {
         if (strictGrammar) {
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 638d81b..6e1987c 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -90,4 +90,11 @@
 
     /** Create an input monitor for gestures. */
     InputMonitor monitorGestureInput(String name, int displayId);
+
+    // Add a runtime association between the input port and the display port. This overrides any
+    // static associations.
+    void addPortAssociation(in String inputPort, int displayPort);
+    // Remove the runtime association between the input port and the display port. Any existing
+    // static association for the cleared input port will be restored.
+    void removePortAssociation(in String inputPort);
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 8d32db0..83f01a5 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -17,6 +17,7 @@
 package android.hardware.input;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemService;
@@ -963,6 +964,41 @@
         }
     }
 
+    /**
+     * Add a runtime association between the input port and the display port. This overrides any
+     * static associations.
+     * @param inputPort The port of the input device.
+     * @param displayPort The physical port of the associated display.
+     * <p>
+     * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT}.
+     * </p>
+     * @hide
+     */
+    public void addPortAssociation(@NonNull String inputPort, int displayPort) {
+        try {
+            mIm.addPortAssociation(inputPort, displayPort);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Remove the runtime association between the input port and the display port. Any existing
+     * static association for the cleared input port will be restored.
+     * @param inputPort The port of the input device to be cleared.
+     * <p>
+     * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT}.
+     * </p>
+     * @hide
+     */
+    public void removePortAssociation(@NonNull String inputPort) {
+        try {
+            mIm.removePortAssociation(inputPort);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
     private void populateInputDevicesLocked() {
         if (mInputDevicesChangedListener == null) {
             final InputDevicesChangedListener listener = new InputDevicesChangedListener();
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 8231c58..d43a619 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -16,9 +16,11 @@
 
 package android.hardware.soundtrigger;
 
+import android.annotation.Nullable;
 import android.hardware.soundtrigger.ModelParams;
 import android.media.AudioFormat;
 import android.media.audio.common.AudioConfig;
+import android.media.soundtrigger_middleware.AudioCapabilities;
 import android.media.soundtrigger_middleware.ConfidenceLevel;
 import android.media.soundtrigger_middleware.ModelParameterRange;
 import android.media.soundtrigger_middleware.Phrase;
@@ -32,8 +34,6 @@
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
 
-import android.annotation.Nullable;
-
 import java.util.Arrays;
 import java.util.UUID;
 
@@ -48,6 +48,7 @@
                 properties.description,
                 properties.uuid,
                 properties.version,
+                properties.supportedModelArch,
                 properties.maxSoundModels,
                 properties.maxKeyPhrases,
                 properties.maxUsers,
@@ -56,7 +57,8 @@
                 properties.maxBufferMs,
                 properties.concurrentCapture,
                 properties.powerConsumptionMw,
-                properties.triggerInEvent
+                properties.triggerInEvent,
+                aidl2apiAudioCapabilities(properties.audioCapabilities)
         );
     }
 
@@ -145,6 +147,7 @@
                     apiConfig.keyphrases[i]);
         }
         aidlConfig.data = Arrays.copyOf(apiConfig.data, apiConfig.data.length);
+        aidlConfig.audioCapabilities = api2aidlAudioCapabilities(apiConfig.audioCapabilities);
         return aidlConfig;
     }
 
@@ -326,4 +329,26 @@
         }
         return new SoundTrigger.ModelParamRange(aidlRange.minInclusive, aidlRange.maxInclusive);
     }
+
+    public static int aidl2apiAudioCapabilities(int aidlCapabilities) {
+        int result = 0;
+        if ((aidlCapabilities & AudioCapabilities.ECHO_CANCELLATION) != 0) {
+            result |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+        }
+        if ((aidlCapabilities & AudioCapabilities.NOISE_SUPPRESSION) != 0) {
+            result |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+        }
+        return result;
+    }
+
+    public static int api2aidlAudioCapabilities(int apiCapabilities) {
+        int result = 0;
+        if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION) != 0) {
+            result |= AudioCapabilities.ECHO_CANCELLATION;
+        }
+        if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION) != 0) {
+            result |= AudioCapabilities.NOISE_SUPPRESSION;
+        }
+        return result;
+    }
 }
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 55505ba..60e466e 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -24,6 +24,7 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -42,6 +43,8 @@
 import android.os.ServiceManager;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.UUID;
@@ -83,6 +86,30 @@
      *
      ****************************************************************************/
     public static final class ModuleProperties implements Parcelable {
+
+        /**
+         * Bit field values of AudioCapabilities supported by the implemented HAL
+         * driver.
+         * @hide
+         */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = {
+                CAPABILITY_ECHO_CANCELLATION,
+                CAPABILITY_NOISE_SUPPRESSION
+        })
+        public @interface AudioCapabilities {}
+
+        /**
+         * If set the underlying module supports AEC.
+         * Describes bit field {@link ModuleProperties#audioCapabilities}
+         */
+        public static final int CAPABILITY_ECHO_CANCELLATION = 0x1;
+        /**
+         * If set, the underlying module supports noise suppression.
+         * Describes bit field {@link ModuleProperties#audioCapabilities}
+         */
+        public static final int CAPABILITY_NOISE_SUPPRESSION = 0x2;
+
         /** Unique module ID provided by the native service */
         public final int id;
 
@@ -101,6 +128,14 @@
         /** Voice detection engine version */
         public final int version;
 
+        /**
+         * String naming the architecture used for running the supported models.
+         * (eg. a platform running models on a DSP could implement this string to convey the DSP
+         * architecture used)
+         */
+        @NonNull
+        public final String supportedModelArch;
+
         /** Maximum number of active sound models */
         public final int maxSoundModels;
 
@@ -129,16 +164,25 @@
          * recognition callback event */
         public final boolean returnsTriggerInEvent;
 
+        /**
+         * Bit field encoding of the AudioCapabilities
+         * supported by the firmware.
+         */
+        @AudioCapabilities
+        public final int audioCapabilities;
+
         ModuleProperties(int id, @NonNull String implementor, @NonNull String description,
-                @NonNull String uuid, int version, int maxSoundModels, int maxKeyphrases,
-                int maxUsers, int recognitionModes, boolean supportsCaptureTransition,
-                int maxBufferMs, boolean supportsConcurrentCapture,
-                int powerConsumptionMw, boolean returnsTriggerInEvent) {
+                @NonNull String uuid, int version, @NonNull String supportedModelArch,
+                int maxSoundModels, int maxKeyphrases, int maxUsers, int recognitionModes,
+                boolean supportsCaptureTransition, int maxBufferMs,
+                boolean supportsConcurrentCapture, int powerConsumptionMw,
+                boolean returnsTriggerInEvent, int audioCapabilities) {
             this.id = id;
             this.implementor = requireNonNull(implementor);
             this.description = requireNonNull(description);
             this.uuid = UUID.fromString(requireNonNull(uuid));
             this.version = version;
+            this.supportedModelArch = requireNonNull(supportedModelArch);
             this.maxSoundModels = maxSoundModels;
             this.maxKeyphrases = maxKeyphrases;
             this.maxUsers = maxUsers;
@@ -148,6 +192,7 @@
             this.supportsConcurrentCapture = supportsConcurrentCapture;
             this.powerConsumptionMw = powerConsumptionMw;
             this.returnsTriggerInEvent = returnsTriggerInEvent;
+            this.audioCapabilities = audioCapabilities;
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<ModuleProperties> CREATOR
@@ -167,6 +212,7 @@
             String description = in.readString();
             String uuid = in.readString();
             int version = in.readInt();
+            String supportedModelArch = in.readString();
             int maxSoundModels = in.readInt();
             int maxKeyphrases = in.readInt();
             int maxUsers = in.readInt();
@@ -176,10 +222,11 @@
             boolean supportsConcurrentCapture = in.readByte() == 1;
             int powerConsumptionMw = in.readInt();
             boolean returnsTriggerInEvent = in.readByte() == 1;
+            int audioCapabilities = in.readInt();
             return new ModuleProperties(id, implementor, description, uuid, version,
-                    maxSoundModels, maxKeyphrases, maxUsers, recognitionModes,
+                    supportedModelArch, maxSoundModels, maxKeyphrases, maxUsers, recognitionModes,
                     supportsCaptureTransition, maxBufferMs, supportsConcurrentCapture,
-                    powerConsumptionMw, returnsTriggerInEvent);
+                    powerConsumptionMw, returnsTriggerInEvent, audioCapabilities);
         }
 
         @Override
@@ -189,6 +236,7 @@
             dest.writeString(description);
             dest.writeString(uuid.toString());
             dest.writeInt(version);
+            dest.writeString(supportedModelArch);
             dest.writeInt(maxSoundModels);
             dest.writeInt(maxKeyphrases);
             dest.writeInt(maxUsers);
@@ -198,6 +246,7 @@
             dest.writeByte((byte) (supportsConcurrentCapture ? 1 : 0));
             dest.writeInt(powerConsumptionMw);
             dest.writeByte((byte) (returnsTriggerInEvent ? 1 : 0));
+            dest.writeInt(audioCapabilities);
         }
 
         @Override
@@ -208,13 +257,15 @@
         @Override
         public String toString() {
             return "ModuleProperties [id=" + id + ", implementor=" + implementor + ", description="
-                    + description + ", uuid=" + uuid + ", version=" + version + ", maxSoundModels="
+                    + description + ", uuid=" + uuid + ", version=" + version
+                    + " , supportedModelArch=" + supportedModelArch + ", maxSoundModels="
                     + maxSoundModels + ", maxKeyphrases=" + maxKeyphrases + ", maxUsers="
                     + maxUsers + ", recognitionModes=" + recognitionModes
                     + ", supportsCaptureTransition=" + supportsCaptureTransition + ", maxBufferMs="
                     + maxBufferMs + ", supportsConcurrentCapture=" + supportsConcurrentCapture
                     + ", powerConsumptionMw=" + powerConsumptionMw
-                    + ", returnsTriggerInEvent=" + returnsTriggerInEvent + "]";
+                    + ", returnsTriggerInEvent=" + returnsTriggerInEvent
+                    + ", audioCapabilities=" + audioCapabilities + "]";
         }
     }
 
@@ -588,19 +639,19 @@
         }
     }
 
-    /*****************************************************************************
+    /**
      * A ModelParamRange is a representation of supported parameter range for a
      * given loaded model.
-     ****************************************************************************/
+     */
     public static final class ModelParamRange implements Parcelable {
 
         /**
-         * start of supported range inclusive
+         * The inclusive start of supported range.
          */
         public final int start;
 
         /**
-         * end of supported range inclusive
+         * The inclusive end of supported range.
          */
         public final int end;
 
@@ -609,31 +660,65 @@
             this.end = end;
         }
 
+        /** @hide */
         private ModelParamRange(@NonNull Parcel in) {
             this.start = in.readInt();
             this.end = in.readInt();
         }
 
         @NonNull
-        public static final Creator<ModelParamRange> CREATOR = new Creator<ModelParamRange>() {
-            @Override
-            @NonNull
-            public ModelParamRange createFromParcel(@NonNull Parcel in) {
-                return new ModelParamRange(in);
-            }
+        public static final Creator<ModelParamRange> CREATOR =
+                new Creator<ModelParamRange>() {
+                    @Override
+                    @NonNull
+                    public ModelParamRange createFromParcel(@NonNull Parcel in) {
+                        return new ModelParamRange(in);
+                    }
 
-            @Override
-            @NonNull
-            public ModelParamRange[] newArray(int size) {
-                return new ModelParamRange[size];
-            }
-        };
+                    @Override
+                    @NonNull
+                    public ModelParamRange[] newArray(int size) {
+                        return new ModelParamRange[size];
+                    }
+                };
 
+        /** @hide */
         @Override
         public int describeContents() {
             return 0;
         }
 
+        /** @hide */
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (start);
+            result = prime * result + (end);
+            return result;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            ModelParamRange other = (ModelParamRange) obj;
+            if (start != other.start) {
+                return false;
+            }
+            if (end != other.end) {
+                return false;
+            }
+            return true;
+        }
+
         @Override
         public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeInt(start);
@@ -1002,13 +1087,27 @@
         @NonNull
         public final byte[] data;
 
-        @UnsupportedAppUsage
+        /**
+         * Bit field encoding of the AudioCapabilities
+         * supported by the firmware.
+         */
+        @ModuleProperties.AudioCapabilities
+        public final int audioCapabilities;
+
         public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
-                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
+                int audioCapabilities) {
             this.captureRequested = captureRequested;
             this.allowMultipleTriggers = allowMultipleTriggers;
             this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
             this.data = data != null ? data : new byte[0];
+            this.audioCapabilities = audioCapabilities;
+        }
+
+        @UnsupportedAppUsage
+        public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
+                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+            this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR
@@ -1028,7 +1127,9 @@
             KeyphraseRecognitionExtra[] keyphrases =
                     in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
             byte[] data = in.readBlob();
-            return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data);
+            int audioCapabilities = in.readInt();
+            return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data,
+                    audioCapabilities);
         }
 
         @Override
@@ -1037,6 +1138,7 @@
             dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0));
             dest.writeTypedArray(keyphrases, flags);
             dest.writeBlob(data);
+            dest.writeInt(audioCapabilities);
         }
 
         @Override
@@ -1048,7 +1150,8 @@
         public String toString() {
             return "RecognitionConfig [captureRequested=" + captureRequested
                     + ", allowMultipleTriggers=" + allowMultipleTriggers + ", keyphrases="
-                    + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data) + "]";
+                    + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data)
+                    + ", audioCapabilities=" + Integer.toHexString(audioCapabilities) + "]";
         }
     }
 
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 73b9d17..67fdda3 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -91,7 +91,7 @@
      *
      * {@hide}
      */
-    @UnsupportedAppUsage
+    @SystemApi
     public static final String ACTION_USB_STATE =
             "android.hardware.usb.action.USB_STATE";
 
@@ -164,7 +164,7 @@
      *
      * {@hide}
      */
-    @UnsupportedAppUsage
+    @SystemApi
     public static final String USB_CONNECTED = "connected";
 
     /**
@@ -181,6 +181,7 @@
      *
      * {@hide}
      */
+    @SystemApi
     public static final String USB_CONFIGURED = "configured";
 
     /**
@@ -217,6 +218,7 @@
      *
      * {@hide}
      */
+    @SystemApi
     public static final String USB_FUNCTION_RNDIS = "rndis";
 
     /**
@@ -319,6 +321,7 @@
      * Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)}
      * {@hide}
      */
+    @SystemApi
     public static final long FUNCTION_NONE = 0;
 
     /**
@@ -337,6 +340,7 @@
      * Code for the rndis usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
      * {@hide}
      */
+    @SystemApi
     public static final long FUNCTION_RNDIS = GadgetFunction.RNDIS;
 
     /**
@@ -698,6 +702,8 @@
      *
      * {@hide}
      */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
     public void setCurrentFunctions(long functions) {
         try {
             mService.setCurrentFunctions(functions);
@@ -737,6 +743,8 @@
      *
      * {@hide}
      */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_USB)
     public long getCurrentFunctions() {
         try {
             return mService.getCurrentFunctions();
diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java
new file mode 100644
index 0000000..6afdb5e
--- /dev/null
+++ b/core/java/android/net/ConnectivityDiagnosticsManager.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.PersistableBundle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * Class that provides utilities for collecting network connectivity diagnostics information.
+ * Connectivity information is made available through triggerable diagnostics tools and by listening
+ * to System validations. Some diagnostics information may be permissions-restricted.
+ *
+ * <p>ConnectivityDiagnosticsManager is intended for use by applications offering network
+ * connectivity on a user device. These tools will provide several mechanisms for these applications
+ * to be alerted to network conditions as well as diagnose potential network issues themselves.
+ *
+ * <p>The primary responsibilities of this class are to:
+ *
+ * <ul>
+ *   <li>Allow permissioned applications to register and unregister callbacks for network event
+ *       notifications
+ *   <li>Invoke callbacks for network event notifications, including:
+ *       <ul>
+ *         <li>Network validations
+ *         <li>Data stalls
+ *         <li>Connectivity reports from applications
+ *       </ul>
+ * </ul>
+ */
+public class ConnectivityDiagnosticsManager {
+    public static final int DETECTION_METHOD_DNS_EVENTS = 1;
+    public static final int DETECTION_METHOD_TCP_METRICS = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            prefix = {"DETECTION_METHOD_"},
+            value = {DETECTION_METHOD_DNS_EVENTS, DETECTION_METHOD_TCP_METRICS})
+    public @interface DetectionMethod {}
+
+    /** @hide */
+    public ConnectivityDiagnosticsManager() {}
+
+    /** Class that includes connectivity information for a specific Network at a specific time. */
+    public static class ConnectivityReport {
+        /** The Network for which this ConnectivityReport applied */
+        @NonNull public final Network network;
+
+        /**
+         * The timestamp for the report. The timestamp is taken from {@link
+         * System#currentTimeMillis}.
+         */
+        public final long reportTimestamp;
+
+        /** LinkProperties available on the Network at the reported timestamp */
+        @NonNull public final LinkProperties linkProperties;
+
+        /** NetworkCapabilities available on the Network at the reported timestamp */
+        @NonNull public final NetworkCapabilities networkCapabilities;
+
+        /** PersistableBundle that may contain additional info about the report */
+        @NonNull public final PersistableBundle additionalInfo;
+
+        /**
+         * Constructor for ConnectivityReport.
+         *
+         * <p>Apps should obtain instances through {@link
+         * ConnectivityDiagnosticsCallback#onConnectivityReport} instead of instantiating their own
+         * instances (unless for testing purposes).
+         *
+         * @param network The Network for which this ConnectivityReport applies
+         * @param reportTimestamp The timestamp for the report
+         * @param linkProperties The LinkProperties available on network at reportTimestamp
+         * @param networkCapabilities The NetworkCapabilities available on network at
+         *     reportTimestamp
+         * @param additionalInfo A PersistableBundle that may contain additional info about the
+         *     report
+         */
+        public ConnectivityReport(
+                @NonNull Network network,
+                long reportTimestamp,
+                @NonNull LinkProperties linkProperties,
+                @NonNull NetworkCapabilities networkCapabilities,
+                @NonNull PersistableBundle additionalInfo) {
+            this.network = network;
+            this.reportTimestamp = reportTimestamp;
+            this.linkProperties = linkProperties;
+            this.networkCapabilities = networkCapabilities;
+            this.additionalInfo = additionalInfo;
+        }
+    }
+
+    /** Class that includes information for a suspected data stall on a specific Network */
+    public static class DataStallReport {
+        /** The Network for which this DataStallReport applied */
+        @NonNull public final Network network;
+
+        /**
+         * The timestamp for the report. The timestamp is taken from {@link
+         * System#currentTimeMillis}.
+         */
+        public final long reportTimestamp;
+
+        /** The detection method used to identify the suspected data stall */
+        @DetectionMethod public final int detectionMethod;
+
+        /** PersistableBundle that may contain additional information on the suspected data stall */
+        @NonNull public final PersistableBundle stallDetails;
+
+        /**
+         * Constructor for DataStallReport.
+         *
+         * <p>Apps should obtain instances through {@link
+         * ConnectivityDiagnosticsCallback#onDataStallSuspected} instead of instantiating their own
+         * instances (unless for testing purposes).
+         *
+         * @param network The Network for which this DataStallReport applies
+         * @param reportTimestamp The timestamp for the report
+         * @param detectionMethod The detection method used to identify this data stall
+         * @param stallDetails A PersistableBundle that may contain additional info about the report
+         */
+        public DataStallReport(
+                @NonNull Network network,
+                long reportTimestamp,
+                @DetectionMethod int detectionMethod,
+                @NonNull PersistableBundle stallDetails) {
+            this.network = network;
+            this.reportTimestamp = reportTimestamp;
+            this.detectionMethod = detectionMethod;
+            this.stallDetails = stallDetails;
+        }
+    }
+
+    /**
+     * Abstract base class for Connectivity Diagnostics callbacks. Used for notifications about
+     * network connectivity events. Must be extended by applications wanting notifications.
+     */
+    public abstract static class ConnectivityDiagnosticsCallback {
+        /**
+         * Called when the platform completes a data connectivity check. This will also be invoked
+         * upon registration with the latest report.
+         *
+         * <p>The Network specified in the ConnectivityReport may not be active any more when this
+         * method is invoked.
+         *
+         * @param report The ConnectivityReport containing information about a connectivity check
+         */
+        public void onConnectivityReport(@NonNull ConnectivityReport report) {}
+
+        /**
+         * Called when the platform suspects a data stall on some Network.
+         *
+         * <p>The Network specified in the DataStallReport may not be active any more when this
+         * method is invoked.
+         *
+         * @param report The DataStallReport containing information about the suspected data stall
+         */
+        public void onDataStallSuspected(@NonNull DataStallReport report) {}
+
+        /**
+         * Called when any app reports connectivity to the System.
+         *
+         * @param network The Network for which connectivity has been reported
+         * @param hasConnectivity The connectivity reported to the System
+         */
+        public void onNetworkConnectivityReported(
+                @NonNull Network network, boolean hasConnectivity) {}
+    }
+
+    /**
+     * Registers a ConnectivityDiagnosticsCallback with the System.
+     *
+     * <p>Only apps that offer network connectivity to the user are allowed to register callbacks.
+     * This includes:
+     *
+     * <ul>
+     *   <li>Carrier apps with active subscriptions
+     *   <li>Active VPNs
+     *   <li>WiFi Suggesters
+     * </ul>
+     *
+     * <p>Callbacks will be limited to receiving notifications for networks over which apps provide
+     * connectivity.
+     *
+     * <p>If a registering app loses its relevant permissions, any callbacks it registered will
+     * silently stop receiving callbacks.
+     *
+     * <p>Each register() call <b>MUST</b> use a unique ConnectivityDiagnosticsCallback instance. If
+     * a single instance is registered with multiple NetworkRequests, an IllegalArgumentException
+     * will be thrown.
+     *
+     * @param request The NetworkRequest that will be used to match with Networks for which
+     *     callbacks will be fired
+     * @param e The Executor to be used for running the callback method invocations
+     * @param callback The ConnectivityDiagnosticsCallback that the caller wants registered with the
+     *     System
+     * @throws IllegalArgumentException if the same callback instance is registered with multiple
+     *     NetworkRequests
+     * @throws SecurityException if the caller does not have appropriate permissions to register a
+     *     callback
+     */
+    public void registerConnectivityDiagnosticsCallback(
+            @NonNull NetworkRequest request,
+            @NonNull Executor e,
+            @NonNull ConnectivityDiagnosticsCallback callback) {
+        // TODO(b/143187964): implement ConnectivityDiagnostics functionality
+        throw new UnsupportedOperationException("registerCallback() not supported yet");
+    }
+
+    /**
+     * Unregisters a ConnectivityDiagnosticsCallback with the System.
+     *
+     * <p>If the given callback is not currently registered with the System, this operation will be
+     * a no-op.
+     *
+     * @param callback The ConnectivityDiagnosticsCallback to be unregistered from the System.
+     */
+    public void unregisterConnectivityDiagnosticsCallback(
+            @NonNull ConnectivityDiagnosticsCallback callback) {
+        // TODO(b/143187964): implement ConnectivityDiagnostics functionality
+        throw new UnsupportedOperationException("registerCallback() not supported yet");
+    }
+}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 6da16a8..4bf3e90 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -363,7 +363,7 @@
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     @UnsupportedAppUsage
     public static final String ACTION_TETHER_STATE_CHANGED =
-            "android.net.conn.TETHER_STATE_CHANGED";
+            TetheringManager.ACTION_TETHER_STATE_CHANGED;
 
     /**
      * @hide
@@ -371,14 +371,14 @@
      * tethering and currently available for tethering.
      */
     @UnsupportedAppUsage
-    public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
+    public static final String EXTRA_AVAILABLE_TETHER = TetheringManager.EXTRA_AVAILABLE_TETHER;
 
     /**
      * @hide
      * gives a String[] listing all the interfaces currently in local-only
      * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding)
      */
-    public static final String EXTRA_ACTIVE_LOCAL_ONLY = "localOnlyArray";
+    public static final String EXTRA_ACTIVE_LOCAL_ONLY = TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
 
     /**
      * @hide
@@ -386,7 +386,7 @@
      * (ie, has DHCPv4 support and packets potentially forwarded/NATed)
      */
     @UnsupportedAppUsage
-    public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
+    public static final String EXTRA_ACTIVE_TETHER = TetheringManager.EXTRA_ACTIVE_TETHER;
 
     /**
      * @hide
@@ -395,7 +395,7 @@
      * for any interfaces listed here.
      */
     @UnsupportedAppUsage
-    public static final String EXTRA_ERRORED_TETHER = "erroredArray";
+    public static final String EXTRA_ERRORED_TETHER = TetheringManager.EXTRA_ERRORED_TETHER;
 
     /**
      * Broadcast Action: The captive portal tracker has finished its test.
@@ -445,7 +445,7 @@
      * @see #startTethering(int, boolean, OnStartTetheringCallback)
      * @hide
      */
-    public static final int TETHERING_INVALID   = -1;
+    public static final int TETHERING_INVALID   = TetheringManager.TETHERING_INVALID;
 
     /**
      * Wifi tethering type.
@@ -453,7 +453,7 @@
      * @hide
      */
     @SystemApi
-    public static final int TETHERING_WIFI      = 0;
+    public static final int TETHERING_WIFI      = TetheringManager.TETHERING_WIFI;
 
     /**
      * USB tethering type.
@@ -461,7 +461,7 @@
      * @hide
      */
     @SystemApi
-    public static final int TETHERING_USB       = 1;
+    public static final int TETHERING_USB       = TetheringManager.TETHERING_USB;
 
     /**
      * Bluetooth tethering type.
@@ -469,7 +469,7 @@
      * @hide
      */
     @SystemApi
-    public static final int TETHERING_BLUETOOTH = 2;
+    public static final int TETHERING_BLUETOOTH = TetheringManager.TETHERING_BLUETOOTH;
 
     /**
      * Wifi P2p tethering type.
@@ -477,41 +477,41 @@
      * need to start from #startTethering(int, boolean, OnStartTetheringCallback).
      * @hide
      */
-    public static final int TETHERING_WIFI_P2P = 3;
+    public static final int TETHERING_WIFI_P2P = TetheringManager.TETHERING_WIFI_P2P;
 
     /**
      * Extra used for communicating with the TetherService. Includes the type of tethering to
      * enable if any.
      * @hide
      */
-    public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
+    public static final String EXTRA_ADD_TETHER_TYPE = TetheringManager.EXTRA_ADD_TETHER_TYPE;
 
     /**
      * Extra used for communicating with the TetherService. Includes the type of tethering for
      * which to cancel provisioning.
      * @hide
      */
-    public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
+    public static final String EXTRA_REM_TETHER_TYPE = TetheringManager.EXTRA_REM_TETHER_TYPE;
 
     /**
      * Extra used for communicating with the TetherService. True to schedule a recheck of tether
      * provisioning.
      * @hide
      */
-    public static final String EXTRA_SET_ALARM = "extraSetAlarm";
+    public static final String EXTRA_SET_ALARM = TetheringManager.EXTRA_SET_ALARM;
 
     /**
      * Tells the TetherService to run a provision check now.
      * @hide
      */
-    public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
+    public static final String EXTRA_RUN_PROVISION = TetheringManager.EXTRA_RUN_PROVISION;
 
     /**
      * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver}
      * which will receive provisioning results. Can be left empty.
      * @hide
      */
-    public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
+    public static final String EXTRA_PROVISION_CALLBACK = TetheringManager.EXTRA_PROVISION_CALLBACK;
 
     /**
      * The absence of a connection type.
@@ -3107,6 +3107,61 @@
         }
     }
 
+    /**
+     * Registers the specified {@link NetworkProvider}.
+     * Each listener must only be registered once. The listener can be unregistered with
+     * {@link #unregisterNetworkProvider}.
+     *
+     * @param provider the provider to register
+     * @return the ID of the provider. This ID must be used by the provider when registering
+     *         {@link android.net.NetworkAgent}s.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public int registerNetworkProvider(@NonNull NetworkProvider provider) {
+        if (provider.getProviderId() != NetworkProvider.ID_NONE) {
+            throw new IllegalStateException("NetworkProviders can only be registered once");
+        }
+
+        try {
+            int providerId = mService.registerNetworkProvider(provider.getMessenger(),
+                    provider.getName());
+            provider.setProviderId(providerId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return provider.getProviderId();
+    }
+
+    /**
+     * Unregisters the specified NetworkProvider.
+     *
+     * @param provider the provider to unregister
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public void unregisterNetworkProvider(@NonNull NetworkProvider provider) {
+        try {
+            mService.unregisterNetworkProvider(provider.getMessenger());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        provider.setProviderId(NetworkProvider.ID_NONE);
+    }
+
+
+    /** @hide exposed via the NetworkProvider class. */
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) {
+        try {
+            mService.declareNetworkRequestUnfulfillable(request);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     // TODO : remove this method. It is a stopgap measure to help sheperding a number
     // of dependent changes that would conflict throughout the automerger graph. Having this
     // temporarily helps with the process of going through with all these dependent changes across
@@ -3118,9 +3173,8 @@
      */
     @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
     public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
-            NetworkCapabilities nc, int score, NetworkMisc misc) {
-        return registerNetworkAgent(messenger, ni, lp, nc, score, misc,
-                NetworkFactory.SerialNumber.NONE);
+            NetworkCapabilities nc, int score, NetworkAgentConfig config) {
+        return registerNetworkAgent(messenger, ni, lp, nc, score, config, NetworkProvider.ID_NONE);
     }
 
     /**
@@ -3130,10 +3184,9 @@
      */
     @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
     public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
-            NetworkCapabilities nc, int score, NetworkMisc misc, int factorySerialNumber) {
+            NetworkCapabilities nc, int score, NetworkAgentConfig config, int providerId) {
         try {
-            return mService.registerNetworkAgent(messenger, ni, lp, nc, score, misc,
-                    factorySerialNumber);
+            return mService.registerNetworkAgent(messenger, ni, lp, nc, score, config, providerId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java
index 98bab44..912df67 100644
--- a/core/java/android/net/DhcpInfo.java
+++ b/core/java/android/net/DhcpInfo.java
@@ -16,8 +16,8 @@
 
 package android.net;
 
-import android.os.Parcelable;
 import android.os.Parcel;
+import android.os.Parcelable;
 
 /**
  * A simple object for retrieving the results of a DHCP request.
@@ -67,12 +67,12 @@
         buf.append(NetworkUtils.intToInetAddress(addr).getHostAddress());
     }
 
-    /** Implement the Parcelable interface {@hide} */
+    /** Implement the Parcelable interface */
     public int describeContents() {
         return 0;
     }
 
-    /** Implement the Parcelable interface {@hide} */
+    /** Implement the Parcelable interface */
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(ipAddress);
         dest.writeInt(gateway);
@@ -83,7 +83,7 @@
         dest.writeInt(leaseDuration);
     }
 
-    /** Implement the Parcelable interface {@hide} */
+    /** Implement the Parcelable interface */
     public static final @android.annotation.NonNull Creator<DhcpInfo> CREATOR =
         new Creator<DhcpInfo>() {
             public DhcpInfo createFromParcel(Parcel in) {
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 09c02ef..7691beb 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -20,9 +20,9 @@
 import android.net.ConnectionInfo;
 import android.net.LinkProperties;
 import android.net.Network;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
-import android.net.NetworkMisc;
 import android.net.NetworkQuotaInfo;
 import android.net.NetworkRequest;
 import android.net.NetworkState;
@@ -142,14 +142,19 @@
 
     void setAirplaneMode(boolean enable);
 
-    int registerNetworkFactory(in Messenger messenger, in String name);
-
     boolean requestBandwidthUpdate(in Network network);
 
+    int registerNetworkFactory(in Messenger messenger, in String name);
     void unregisterNetworkFactory(in Messenger messenger);
 
+    int registerNetworkProvider(in Messenger messenger, in String name);
+    void unregisterNetworkProvider(in Messenger messenger);
+
+    void declareNetworkRequestUnfulfillable(in NetworkRequest request);
+
     int registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp,
-            in NetworkCapabilities nc, int score, in NetworkMisc misc, in int factorySerialNumber);
+            in NetworkCapabilities nc, int score, in NetworkAgentConfig config,
+            in int factorySerialNumber);
 
     NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities,
             in Messenger messenger, int timeoutSec, in IBinder binder, int legacy);
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 9994f9f..5fa515a 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -23,6 +23,8 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
+import android.net.netstats.provider.INetworkStatsProvider;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.os.IBinder;
 import android.os.Messenger;
 import com.android.internal.net.VpnInfo;
@@ -89,4 +91,7 @@
     /** Get the total network stats information since boot */
     long getTotalStats(int type);
 
+    /** Registers a network stats provider */
+    INetworkStatsProviderCallback registerNetworkStatsProvider(String tag,
+            in INetworkStatsProvider provider);
 }
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 45d0c73..09ec6c3 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -17,7 +17,6 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
@@ -26,6 +25,7 @@
 import android.annotation.TestApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.net.annotations.PolicyDirection;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -41,8 +41,6 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.net.DatagramSocket;
 import java.net.InetAddress;
 import java.net.Socket;
@@ -78,11 +76,6 @@
      */
     public static final int DIRECTION_OUT = 1;
 
-    /** @hide */
-    @IntDef(value = {DIRECTION_IN, DIRECTION_OUT})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface PolicyDirection {}
-
     /**
      * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index.
      *
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 60bd573..fc72eec 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -43,11 +43,12 @@
  *
  * @hide
  */
-public abstract class NetworkAgent extends Handler {
+public abstract class NetworkAgent {
     // Guaranteed to be valid (not NETID_UNSET), otherwise registerNetworkAgent() would have thrown
     // an exception.
     public final int netId;
 
+    private final Handler mHandler;
     private volatile AsyncChannel mAsyncChannel;
     private final String LOG_TAG;
     private static final boolean DBG = true;
@@ -58,7 +59,7 @@
     private static final long BW_REFRESH_MIN_WIN_MS = 500;
     private boolean mPollLceScheduled = false;
     private AtomicBoolean mPollLcePending = new AtomicBoolean(false);
-    public final int mFactorySerialNumber;
+    public final int mProviderId;
 
     private static final int BASE = Protocol.BASE_NETWORK_AGENT;
 
@@ -219,25 +220,25 @@
     // the entire tree.
     public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
             NetworkCapabilities nc, LinkProperties lp, int score) {
-        this(looper, context, logTag, ni, nc, lp, score, null, NetworkFactory.SerialNumber.NONE);
+        this(looper, context, logTag, ni, nc, lp, score, null, NetworkProvider.ID_NONE);
     }
     public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
-            NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) {
-        this(looper, context, logTag, ni, nc, lp, score, misc, NetworkFactory.SerialNumber.NONE);
+            NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config) {
+        this(looper, context, logTag, ni, nc, lp, score, config, NetworkProvider.ID_NONE);
     }
 
     public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
-            NetworkCapabilities nc, LinkProperties lp, int score, int factorySerialNumber) {
-        this(looper, context, logTag, ni, nc, lp, score, null, factorySerialNumber);
+            NetworkCapabilities nc, LinkProperties lp, int score, int providerId) {
+        this(looper, context, logTag, ni, nc, lp, score, null, providerId);
     }
 
     public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
-            NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc,
-            int factorySerialNumber) {
-        super(looper);
+            NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config,
+            int providerId) {
+        mHandler = new NetworkAgentHandler(looper);
         LOG_TAG = logTag;
         mContext = context;
-        mFactorySerialNumber = factorySerialNumber;
+        mProviderId = providerId;
         if (ni == null || nc == null || lp == null) {
             throw new IllegalArgumentException();
         }
@@ -245,117 +246,124 @@
         if (VDBG) log("Registering NetworkAgent");
         ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
                 Context.CONNECTIVITY_SERVICE);
-        netId = cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
-                new LinkProperties(lp), new NetworkCapabilities(nc), score, misc,
-                factorySerialNumber);
+        netId = cm.registerNetworkAgent(new Messenger(mHandler), new NetworkInfo(ni),
+                new LinkProperties(lp), new NetworkCapabilities(nc), score, config,
+                providerId);
     }
 
-    @Override
-    public void handleMessage(Message msg) {
-        switch (msg.what) {
-            case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
-                if (mAsyncChannel != null) {
-                    log("Received new connection while already connected!");
-                } else {
-                    if (VDBG) log("NetworkAgent fully connected");
-                    AsyncChannel ac = new AsyncChannel();
-                    ac.connected(null, this, msg.replyTo);
-                    ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
-                            AsyncChannel.STATUS_SUCCESSFUL);
-                    synchronized (mPreConnectedQueue) {
-                        mAsyncChannel = ac;
-                        for (Message m : mPreConnectedQueue) {
-                            ac.sendMessage(m);
-                        }
-                        mPreConnectedQueue.clear();
-                    }
-                }
-                break;
-            }
-            case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
-                if (VDBG) log("CMD_CHANNEL_DISCONNECT");
-                if (mAsyncChannel != null) mAsyncChannel.disconnect();
-                break;
-            }
-            case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
-                if (DBG) log("NetworkAgent channel lost");
-                // let the client know CS is done with us.
-                unwanted();
-                synchronized (mPreConnectedQueue) {
-                    mAsyncChannel = null;
-                }
-                break;
-            }
-            case CMD_SUSPECT_BAD: {
-                log("Unhandled Message " + msg);
-                break;
-            }
-            case CMD_REQUEST_BANDWIDTH_UPDATE: {
-                long currentTimeMs = System.currentTimeMillis();
-                if (VDBG) {
-                    log("CMD_REQUEST_BANDWIDTH_UPDATE request received.");
-                }
-                if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) {
-                    mPollLceScheduled = false;
-                    if (mPollLcePending.getAndSet(true) == false) {
-                        pollLceData();
-                    }
-                } else {
-                    // deliver the request at a later time rather than discard it completely.
-                    if (!mPollLceScheduled) {
-                        long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS -
-                                currentTimeMs + 1;
-                        mPollLceScheduled = sendEmptyMessageDelayed(
-                                CMD_REQUEST_BANDWIDTH_UPDATE, waitTime);
-                    }
-                }
-                break;
-            }
-            case CMD_REPORT_NETWORK_STATUS: {
-                String redirectUrl = ((Bundle)msg.obj).getString(REDIRECT_URL_KEY);
-                if (VDBG) {
-                    log("CMD_REPORT_NETWORK_STATUS(" +
-                            (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ") + redirectUrl);
-                }
-                networkStatus(msg.arg1, redirectUrl);
-                break;
-            }
-            case CMD_SAVE_ACCEPT_UNVALIDATED: {
-                saveAcceptUnvalidated(msg.arg1 != 0);
-                break;
-            }
-            case CMD_START_SOCKET_KEEPALIVE: {
-                startSocketKeepalive(msg);
-                break;
-            }
-            case CMD_STOP_SOCKET_KEEPALIVE: {
-                stopSocketKeepalive(msg);
-                break;
-            }
+    private class NetworkAgentHandler extends Handler {
+        NetworkAgentHandler(Looper looper) {
+            super(looper);
+        }
 
-            case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: {
-                ArrayList<Integer> thresholds =
-                        ((Bundle) msg.obj).getIntegerArrayList("thresholds");
-                // TODO: Change signal strength thresholds API to use an ArrayList<Integer>
-                // rather than convert to int[].
-                int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0];
-                for (int i = 0; i < intThresholds.length; i++) {
-                    intThresholds[i] = thresholds.get(i);
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
+                    if (mAsyncChannel != null) {
+                        log("Received new connection while already connected!");
+                    } else {
+                        if (VDBG) log("NetworkAgent fully connected");
+                        AsyncChannel ac = new AsyncChannel();
+                        ac.connected(null, this, msg.replyTo);
+                        ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+                                AsyncChannel.STATUS_SUCCESSFUL);
+                        synchronized (mPreConnectedQueue) {
+                            mAsyncChannel = ac;
+                            for (Message m : mPreConnectedQueue) {
+                                ac.sendMessage(m);
+                            }
+                            mPreConnectedQueue.clear();
+                        }
+                    }
+                    break;
                 }
-                setSignalStrengthThresholds(intThresholds);
-                break;
-            }
-            case CMD_PREVENT_AUTOMATIC_RECONNECT: {
-                preventAutomaticReconnect();
-                break;
-            }
-            case CMD_ADD_KEEPALIVE_PACKET_FILTER: {
-                addKeepalivePacketFilter(msg);
-                break;
-            }
-            case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: {
-                removeKeepalivePacketFilter(msg);
-                break;
+                case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
+                    if (VDBG) log("CMD_CHANNEL_DISCONNECT");
+                    if (mAsyncChannel != null) mAsyncChannel.disconnect();
+                    break;
+                }
+                case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+                    if (DBG) log("NetworkAgent channel lost");
+                    // let the client know CS is done with us.
+                    unwanted();
+                    synchronized (mPreConnectedQueue) {
+                        mAsyncChannel = null;
+                    }
+                    break;
+                }
+                case CMD_SUSPECT_BAD: {
+                    log("Unhandled Message " + msg);
+                    break;
+                }
+                case CMD_REQUEST_BANDWIDTH_UPDATE: {
+                    long currentTimeMs = System.currentTimeMillis();
+                    if (VDBG) {
+                        log("CMD_REQUEST_BANDWIDTH_UPDATE request received.");
+                    }
+                    if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) {
+                        mPollLceScheduled = false;
+                        if (!mPollLcePending.getAndSet(true)) {
+                            pollLceData();
+                        }
+                    } else {
+                        // deliver the request at a later time rather than discard it completely.
+                        if (!mPollLceScheduled) {
+                            long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS
+                                    - currentTimeMs + 1;
+                            mPollLceScheduled = sendEmptyMessageDelayed(
+                                    CMD_REQUEST_BANDWIDTH_UPDATE, waitTime);
+                        }
+                    }
+                    break;
+                }
+                case CMD_REPORT_NETWORK_STATUS: {
+                    String redirectUrl = ((Bundle) msg.obj).getString(REDIRECT_URL_KEY);
+                    if (VDBG) {
+                        log("CMD_REPORT_NETWORK_STATUS("
+                                + (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ")
+                                + redirectUrl);
+                    }
+                    networkStatus(msg.arg1, redirectUrl);
+                    break;
+                }
+                case CMD_SAVE_ACCEPT_UNVALIDATED: {
+                    saveAcceptUnvalidated(msg.arg1 != 0);
+                    break;
+                }
+                case CMD_START_SOCKET_KEEPALIVE: {
+                    startSocketKeepalive(msg);
+                    break;
+                }
+                case CMD_STOP_SOCKET_KEEPALIVE: {
+                    stopSocketKeepalive(msg);
+                    break;
+                }
+
+                case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: {
+                    ArrayList<Integer> thresholds =
+                            ((Bundle) msg.obj).getIntegerArrayList("thresholds");
+                    // TODO: Change signal strength thresholds API to use an ArrayList<Integer>
+                    // rather than convert to int[].
+                    int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0];
+                    for (int i = 0; i < intThresholds.length; i++) {
+                        intThresholds[i] = thresholds.get(i);
+                    }
+                    setSignalStrengthThresholds(intThresholds);
+                    break;
+                }
+                case CMD_PREVENT_AUTOMATIC_RECONNECT: {
+                    preventAutomaticReconnect();
+                    break;
+                }
+                case CMD_ADD_KEEPALIVE_PACKET_FILTER: {
+                    addKeepalivePacketFilter(msg);
+                    break;
+                }
+                case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: {
+                    removeKeepalivePacketFilter(msg);
+                    break;
+                }
             }
         }
     }
diff --git a/core/java/android/net/NetworkMisc.aidl b/core/java/android/net/NetworkAgentConfig.aidl
similarity index 95%
rename from core/java/android/net/NetworkMisc.aidl
rename to core/java/android/net/NetworkAgentConfig.aidl
index c65583f..cb70bdd 100644
--- a/core/java/android/net/NetworkMisc.aidl
+++ b/core/java/android/net/NetworkAgentConfig.aidl
@@ -16,4 +16,4 @@
 
 package android.net;
 
-parcelable NetworkMisc;
+parcelable NetworkAgentConfig;
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkAgentConfig.java
similarity index 72%
rename from core/java/android/net/NetworkMisc.java
rename to core/java/android/net/NetworkAgentConfig.java
index 4ad52d5..3a383a4 100644
--- a/core/java/android/net/NetworkMisc.java
+++ b/core/java/android/net/NetworkAgentConfig.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -26,7 +28,7 @@
  *
  * @hide
  */
-public class NetworkMisc implements Parcelable {
+public class NetworkAgentConfig implements Parcelable {
 
     /**
      * If the {@link Network} is a VPN, whether apps are allowed to bypass the
@@ -83,17 +85,17 @@
      */
     public boolean hasShownBroken;
 
-    public NetworkMisc() {
+    public NetworkAgentConfig() {
     }
 
-    public NetworkMisc(NetworkMisc nm) {
-        if (nm != null) {
-            allowBypass = nm.allowBypass;
-            explicitlySelected = nm.explicitlySelected;
-            acceptUnvalidated = nm.acceptUnvalidated;
-            subscriberId = nm.subscriberId;
-            provisioningNotificationDisabled = nm.provisioningNotificationDisabled;
-            skip464xlat = nm.skip464xlat;
+    public NetworkAgentConfig(@Nullable NetworkAgentConfig nac) {
+        if (nac != null) {
+            allowBypass = nac.allowBypass;
+            explicitlySelected = nac.explicitlySelected;
+            acceptUnvalidated = nac.acceptUnvalidated;
+            subscriberId = nac.subscriberId;
+            provisioningNotificationDisabled = nac.provisioningNotificationDisabled;
+            skip464xlat = nac.skip464xlat;
         }
     }
 
@@ -112,22 +114,23 @@
         out.writeInt(skip464xlat ? 1 : 0);
     }
 
-    public static final @android.annotation.NonNull Creator<NetworkMisc> CREATOR = new Creator<NetworkMisc>() {
+    public static final @NonNull Creator<NetworkAgentConfig> CREATOR =
+            new Creator<NetworkAgentConfig>() {
         @Override
-        public NetworkMisc createFromParcel(Parcel in) {
-            NetworkMisc networkMisc = new NetworkMisc();
-            networkMisc.allowBypass = in.readInt() != 0;
-            networkMisc.explicitlySelected = in.readInt() != 0;
-            networkMisc.acceptUnvalidated = in.readInt() != 0;
-            networkMisc.subscriberId = in.readString();
-            networkMisc.provisioningNotificationDisabled = in.readInt() != 0;
-            networkMisc.skip464xlat = in.readInt() != 0;
-            return networkMisc;
+        public NetworkAgentConfig createFromParcel(Parcel in) {
+            NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
+            networkAgentConfig.allowBypass = in.readInt() != 0;
+            networkAgentConfig.explicitlySelected = in.readInt() != 0;
+            networkAgentConfig.acceptUnvalidated = in.readInt() != 0;
+            networkAgentConfig.subscriberId = in.readString();
+            networkAgentConfig.provisioningNotificationDisabled = in.readInt() != 0;
+            networkAgentConfig.skip464xlat = in.readInt() != 0;
+            return networkAgentConfig;
         }
 
         @Override
-        public NetworkMisc[] newArray(int size) {
-            return new NetworkMisc[size];
+        public NetworkAgentConfig[] newArray(int size) {
+            return new NetworkAgentConfig[size];
         }
     };
 }
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 33c39d4..739e817 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -587,15 +587,14 @@
     }
 
     /**
-     * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if all the capabilities it provides are
-     * typically provided by restricted networks.
+     * Deduces that all the capabilities it provides are typically provided by restricted networks
+     * or not.
      *
-     * TODO: consider:
-     * - Renaming it to guessRestrictedCapability and make it set the
-     *   restricted capability bit in addition to clearing it.
+     * @return {@code true} if the network should be restricted.
      * @hide
      */
-    public void maybeMarkCapabilitiesRestricted() {
+    @SystemApi
+    public boolean deduceRestrictedCapability() {
         // Check if we have any capability that forces the network to be restricted.
         final boolean forceRestrictedCapability =
                 (mNetworkCapabilities & FORCE_RESTRICTED_CAPABILITIES) != 0;
@@ -609,8 +608,17 @@
         final boolean hasRestrictedCapabilities =
                 (mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0;
 
-        if (forceRestrictedCapability
-                || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities)) {
+        return forceRestrictedCapability
+                || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities);
+    }
+
+    /**
+     * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if deducing the network is restricted.
+     *
+     * @hide
+     */
+    public void maybeMarkCapabilitiesRestricted() {
+        if (deduceRestrictedCapability()) {
             removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
         }
     }
diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java
index 42ab925..824ddb8 100644
--- a/core/java/android/net/NetworkFactory.java
+++ b/core/java/android/net/NetworkFactory.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Build;
@@ -27,7 +28,6 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Protocol;
 
@@ -52,7 +52,7 @@
  * @hide
  **/
 public class NetworkFactory extends Handler {
-    /** @hide */
+    /* TODO: delete when all callers have migrated to NetworkProvider IDs. */
     public static class SerialNumber {
         // Guard used by no network factory.
         public static final int NONE = -1;
@@ -91,8 +91,8 @@
      *            with the same NetworkRequest but an updated score.
      *            Also, network conditions may change for this bearer
      *            allowing for a better score in the future.
-     * msg.arg2 = the serial number of the factory currently responsible for the
-     *            NetworkAgent handling this request, or SerialNumber.NONE if none.
+     * msg.arg2 = the ID of the NetworkProvider currently responsible for the
+     *            NetworkAgent handling this request, or NetworkProvider.ID_NONE if none.
      */
     public static final int CMD_REQUEST_NETWORK = BASE;
 
@@ -124,7 +124,6 @@
 
     private final Context mContext;
     private final ArrayList<Message> mPreConnectedQueue = new ArrayList<Message>();
-    private AsyncChannel mAsyncChannel;
     private final String LOG_TAG;
 
     private final SparseArray<NetworkRequestInfo> mNetworkRequests =
@@ -135,7 +134,8 @@
 
     private int mRefCount = 0;
     private Messenger mMessenger = null;
-    private int mSerialNumber;
+    private NetworkProvider mProvider = null;
+    private int mProviderId;
 
     @UnsupportedAppUsage
     public NetworkFactory(Looper looper, Context context, String logTag,
@@ -147,55 +147,43 @@
     }
 
     public void register() {
-        if (DBG) log("Registering NetworkFactory");
-        if (mMessenger == null) {
-            mMessenger = new Messenger(this);
-            mSerialNumber = ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger,
-                    LOG_TAG);
+        if (mProvider != null) {
+            Log.e(LOG_TAG, "Ignoring attempt to register already-registered NetworkFactory");
+            return;
         }
+        if (DBG) log("Registering NetworkFactory");
+
+        mProvider = new NetworkProvider(mContext, NetworkFactory.this.getLooper(), LOG_TAG) {
+            @Override
+            public void onNetworkRequested(@NonNull NetworkRequest request, int score,
+                    int servingProviderId) {
+                handleAddRequest((NetworkRequest) request, score, servingProviderId);
+            }
+
+            @Override
+            public void onRequestWithdrawn(@NonNull NetworkRequest request) {
+                handleRemoveRequest(request);
+            }
+        };
+
+        mMessenger = new Messenger(this);
+        mProviderId = ConnectivityManager.from(mContext).registerNetworkProvider(mProvider);
     }
 
     public void unregister() {
-        if (DBG) log("Unregistering NetworkFactory");
-        if (mMessenger != null) {
-            ConnectivityManager.from(mContext).unregisterNetworkFactory(mMessenger);
-            mMessenger = null;
+        if (mProvider == null) {
+            Log.e(LOG_TAG, "Ignoring attempt to unregister unregistered NetworkFactory");
+            return;
         }
+        if (DBG) log("Unregistering NetworkFactory");
+
+        ConnectivityManager.from(mContext).unregisterNetworkProvider(mProvider);
+        mProvider = null;
     }
 
     @Override
     public void handleMessage(Message msg) {
         switch (msg.what) {
-            case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
-                if (mAsyncChannel != null) {
-                    log("Received new connection while already connected!");
-                    break;
-                }
-                if (VDBG) log("NetworkFactory fully connected");
-                AsyncChannel ac = new AsyncChannel();
-                ac.connected(null, this, msg.replyTo);
-                ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
-                        AsyncChannel.STATUS_SUCCESSFUL);
-                mAsyncChannel = ac;
-                for (Message m : mPreConnectedQueue) {
-                    ac.sendMessage(m);
-                }
-                mPreConnectedQueue.clear();
-                break;
-            }
-            case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
-                if (VDBG) log("CMD_CHANNEL_DISCONNECT");
-                if (mAsyncChannel != null) {
-                    mAsyncChannel.disconnect();
-                    mAsyncChannel = null;
-                }
-                break;
-            }
-            case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
-                if (DBG) log("NetworkFactory channel lost");
-                mAsyncChannel = null;
-                break;
-            }
             case CMD_REQUEST_NETWORK: {
                 handleAddRequest((NetworkRequest) msg.obj, msg.arg1, msg.arg2);
                 break;
@@ -219,13 +207,13 @@
         public final NetworkRequest request;
         public int score;
         public boolean requested; // do we have a request outstanding, limited by score
-        public int factorySerialNumber;
+        public int providerId;
 
-        NetworkRequestInfo(NetworkRequest request, int score, int factorySerialNumber) {
+        NetworkRequestInfo(NetworkRequest request, int score, int providerId) {
             this.request = request;
             this.score = score;
             this.requested = false;
-            this.factorySerialNumber = factorySerialNumber;
+            this.providerId = providerId;
         }
 
         @Override
@@ -240,8 +228,6 @@
      *
      * @param request the request to handle.
      * @param score the score of the NetworkAgent currently satisfying this request.
-     * @param servingFactorySerialNumber the serial number of the NetworkFactory that
-     *         created the NetworkAgent currently satisfying this request.
      */
     // TODO : remove this method. It is a stopgap measure to help sheperding a number
     // of dependent changes that would conflict throughout the automerger graph. Having this
@@ -249,7 +235,7 @@
     // the entire tree.
     @VisibleForTesting
     protected void handleAddRequest(NetworkRequest request, int score) {
-        handleAddRequest(request, score, SerialNumber.NONE);
+        handleAddRequest(request, score, NetworkProvider.ID_NONE);
     }
 
     /**
@@ -258,27 +244,26 @@
      *
      * @param request the request to handle.
      * @param score the score of the NetworkAgent currently satisfying this request.
-     * @param servingFactorySerialNumber the serial number of the NetworkFactory that
-     *         created the NetworkAgent currently satisfying this request.
+     * @param servingProviderId the ID of the NetworkProvider that created the NetworkAgent
+     *        currently satisfying this request.
      */
     @VisibleForTesting
-    protected void handleAddRequest(NetworkRequest request, int score,
-            int servingFactorySerialNumber) {
+    protected void handleAddRequest(NetworkRequest request, int score, int servingProviderId) {
         NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
         if (n == null) {
             if (DBG) {
                 log("got request " + request + " with score " + score
-                        + " and serial " + servingFactorySerialNumber);
+                        + " and providerId " + servingProviderId);
             }
-            n = new NetworkRequestInfo(request, score, servingFactorySerialNumber);
+            n = new NetworkRequestInfo(request, score, servingProviderId);
             mNetworkRequests.put(n.request.requestId, n);
         } else {
             if (VDBG) {
                 log("new score " + score + " for exisiting request " + request
-                        + " with serial " + servingFactorySerialNumber);
+                        + " and providerId " + servingProviderId);
             }
             n.score = score;
-            n.factorySerialNumber = servingFactorySerialNumber;
+            n.providerId = servingProviderId;
         }
         if (VDBG) log("  my score=" + mScore + ", my filter=" + mCapabilityFilter);
 
@@ -333,8 +318,8 @@
             log(" n.requests = " + n.requested);
             log(" n.score = " + n.score);
             log(" mScore = " + mScore);
-            log(" n.factorySerialNumber = " + n.factorySerialNumber);
-            log(" mSerialNumber = " + mSerialNumber);
+            log(" n.providerId = " + n.providerId);
+            log(" mProviderId = " + mProviderId);
         }
         if (shouldNeedNetworkFor(n)) {
             if (VDBG) log("  needNetworkFor");
@@ -355,7 +340,7 @@
             // If the score of this request is higher or equal to that of this factory and some
             // other factory is responsible for it, then this factory should not track the request
             // because it has no hope of satisfying it.
-            && (n.score < mScore || n.factorySerialNumber == mSerialNumber)
+            && (n.score < mScore || n.providerId == mProviderId)
             // If this factory can't satisfy the capability needs of this request, then it
             // should not be tracked.
             && n.request.networkCapabilities.satisfiedByNetworkCapabilities(mCapabilityFilter)
@@ -373,7 +358,7 @@
             //   assigned to the factory
             // - This factory can't satisfy the capability needs of the request
             // - The concrete implementation of the factory rejects the request
-            && ((n.score > mScore && n.factorySerialNumber != mSerialNumber)
+            && ((n.score > mScore && n.providerId != mProviderId)
                     || !n.request.networkCapabilities.satisfiedByNetworkCapabilities(
                             mCapabilityFilter)
                     || !acceptRequest(n.request, n.score));
@@ -408,12 +393,7 @@
     protected void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r) {
         post(() -> {
             if (DBG) log("releaseRequestAsUnfulfillableByAnyFactory: " + r);
-            Message msg = obtainMessage(EVENT_UNFULFILLABLE_REQUEST, r);
-            if (mAsyncChannel != null) {
-                mAsyncChannel.sendMessage(msg);
-            } else {
-                mPreConnectedQueue.add(msg);
-            }
+            ConnectivityManager.from(mContext).declareNetworkRequestUnfulfillable(r);
         });
     }
 
@@ -444,8 +424,13 @@
         return mNetworkRequests.size();
     }
 
+    /* TODO: delete when all callers have migrated to NetworkProvider IDs. */
     public int getSerialNumber() {
-        return mSerialNumber;
+        return mProviderId;
+    }
+
+    public int getProviderId() {
+        return mProviderId;
     }
 
     protected void log(String s) {
@@ -465,8 +450,8 @@
 
     @Override
     public String toString() {
-        StringBuilder sb = new StringBuilder("{").append(LOG_TAG).append(" - mSerialNumber=")
-                .append(mSerialNumber).append(", ScoreFilter=")
+        StringBuilder sb = new StringBuilder("{").append(LOG_TAG).append(" - mProviderId=")
+                .append(mProviderId).append(", ScoreFilter=")
                 .append(mScore).append(", Filter=").append(mCapabilityFilter).append(", requests=")
                 .append(mNetworkRequests.size()).append(", refCount=").append(mRefCount)
                 .append("}");
diff --git a/core/java/android/net/NetworkProvider.java b/core/java/android/net/NetworkProvider.java
new file mode 100644
index 0000000..2c0e4aa
--- /dev/null
+++ b/core/java/android/net/NetworkProvider.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.util.Log;
+
+/**
+ * Base class for network providers such as telephony or Wi-Fi. NetworkProviders connect the device
+ * to networks and makes them available to to the core network stack by creating
+ * {@link NetworkAgent}s. The networks can then provide connectivity to apps and can be interacted
+ * with via networking APIs such as {@link ConnectivityManager}.
+ *
+ * Subclasses should implement {@link #onNetworkRequested} and {@link #onRequestWithdrawn} to
+ * receive {@link NetworkRequest}s sent by the system and by apps. A network that is not the
+ * best (highest-scoring) network for any request is generally not used by the system, and torn
+ * down.
+ *
+ * @hide
+ */
+@SystemApi
+public class NetworkProvider {
+    /**
+     * {@code providerId} value that indicates the absence of a provider. It is the providerId of
+     * any NetworkProvider that is not currently registered, and of any NetworkRequest that is not
+     * currently being satisfied by a network.
+     */
+    public static final int ID_NONE = -1;
+
+    /**
+     * A hardcoded ID for NetworkAgents representing VPNs. These agents are not created by any
+     * provider, so they use this constant for clarity instead of NONE.
+     * @hide only used by ConnectivityService.
+     */
+    public static final int ID_VPN = -2;
+
+    /**
+     * The first providerId value that will be allocated.
+     * @hide only used by ConnectivityService.
+     */
+    public static final int FIRST_PROVIDER_ID = 1;
+
+    /** @hide only used by ConnectivityService */
+    public static final int CMD_REQUEST_NETWORK = 1;
+    /** @hide only used by ConnectivityService */
+    public static final int CMD_CANCEL_REQUEST = 2;
+
+    private final Messenger mMessenger;
+    private final String mName;
+    private final ConnectivityManager mCm;
+
+    private int mProviderId = ID_NONE;
+
+    /**
+     * Constructs a new NetworkProvider.
+     *
+     * @param looper the Looper on which to run {@link #onNetworkRequested} and
+     *               {@link #onRequestWithdrawn}.
+     * @param name the name of the listener, used only for debugging.
+     *
+     * @hide
+     */
+    @SystemApi
+    public NetworkProvider(@NonNull Context context, @NonNull Looper looper, @NonNull String name) {
+        mCm = ConnectivityManager.from(context);
+
+        Handler handler = new Handler(looper) {
+            @Override
+            public void handleMessage(Message m) {
+                switch (m.what) {
+                    case CMD_REQUEST_NETWORK:
+                        onNetworkRequested((NetworkRequest) m.obj, m.arg1, m.arg2);
+                        break;
+                    case CMD_CANCEL_REQUEST:
+                        onRequestWithdrawn((NetworkRequest) m.obj);
+                        break;
+                    default:
+                        Log.e(mName, "Unhandled message: " + m.what);
+                }
+            }
+        };
+        mMessenger = new Messenger(handler);
+        mName = name;
+    }
+
+    // TODO: consider adding a register() method so ConnectivityManager does not need to call this.
+    public @Nullable Messenger getMessenger() {
+        return mMessenger;
+    }
+
+    public @NonNull String getName() {
+        return mName;
+    }
+
+    /**
+     * Returns the ID of this provider. This is known only once the provider is registered via
+     * {@link ConnectivityManager#registerNetworkProvider()}, otherwise the ID is {@link #ID_NONE}.
+     * This ID must be used when registering any {@link NetworkAgent}s.
+     */
+    public int getProviderId() {
+        return mProviderId;
+    }
+
+    /** @hide */
+    public void setProviderId(int providerId) {
+        mProviderId = providerId;
+    }
+
+    /**
+     *  Called when a NetworkRequest is received. The request may be a new request or an existing
+     *  request with a different score.
+     *
+     * @param request the NetworkRequest being received
+     * @param score the score of the network currently satisfying the request, or 0 if none.
+     * @param providerId the ID of the provider that created the network currently satisfying this
+     *                   request, or {@link #ID_NONE} if none.
+     *
+     *  @hide
+     */
+    @SystemApi
+    public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {}
+
+    /**
+     *  Called when a NetworkRequest is withdrawn.
+     *  @hide
+     */
+    @SystemApi
+    public void onRequestWithdrawn(@NonNull NetworkRequest request) {}
+
+    /**
+     * Asserts that no provider will ever be able to satisfy the specified request. The provider
+     * must only call this method if it knows that it is the only provider on the system capable of
+     * satisfying this request, and that the request cannot be satisfied. The application filing the
+     * request will receive an {@link NetworkCallback#onUnavailable()} callback.
+     *
+     * @param request the request that cannot be fulfilled
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+    public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) {
+        mCm.declareNetworkRequestUnfulfillable(request);
+    }
+}
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 431773d..9731f3c 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -247,9 +247,8 @@
          * removing even the capabilities that are set by default when the object is constructed.
          *
          * @return The builder to facilitate chaining.
-         * @hide
          */
-        @UnsupportedAppUsage
+        @NonNull
         public Builder clearCapabilities() {
             mNetworkCapabilities.clearAll();
             return this;
diff --git a/core/java/android/net/NetworkSpecifier.java b/core/java/android/net/NetworkSpecifier.java
index 2bc3eb5..cf31d21 100644
--- a/core/java/android/net/NetworkSpecifier.java
+++ b/core/java/android/net/NetworkSpecifier.java
@@ -16,6 +16,9 @@
 
 package android.net;
 
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
 /**
  * Describes specific properties of a requested network for use in a {@link NetworkRequest}.
  *
@@ -31,7 +34,8 @@
      *
      * @hide
      */
-    public abstract boolean satisfiedBy(NetworkSpecifier other);
+    @SystemApi
+    public abstract boolean satisfiedBy(@Nullable NetworkSpecifier other);
 
     /**
      * Optional method which can be overridden by concrete implementations of NetworkSpecifier to
@@ -45,6 +49,7 @@
      *
      * @hide
      */
+    @SystemApi
     public void assertValidFromUid(int requestorUid) {
         // empty
     }
@@ -68,6 +73,8 @@
      *
      * @hide
      */
+    @SystemApi
+    @Nullable
     public NetworkSpecifier redact() {
         // TODO (b/122160111): convert default to null once all platform NetworkSpecifiers
         // implement this method.
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 45bf4d2..96d7a80 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -19,6 +19,9 @@
 import static android.os.Process.CLAT_UID;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -48,59 +51,104 @@
  * @hide
  */
 // @NotThreadSafe
-public class NetworkStats implements Parcelable {
+@SystemApi
+public final class NetworkStats implements Parcelable {
     private static final String TAG = "NetworkStats";
+
     /** {@link #iface} value when interface details unavailable. */
-    public static final String IFACE_ALL = null;
+    @SuppressLint("CompileTimeConstant")
+    @Nullable public static final String IFACE_ALL = null;
+    /**
+     * Virtual network interface for video telephony. This is for VT data usage counting
+     * purpose.
+     */
+    public static final String IFACE_VT = "vt_data0";
+
     /** {@link #uid} value when UID details unavailable. */
     public static final int UID_ALL = -1;
-    /** {@link #tag} value matching any tag. */
+    /** Special UID value for data usage by tethering. */
+    public static final int UID_TETHERING = -5;
+
+    /**
+     * {@link #tag} value matching any tag.
+     * @hide
+     */
     // TODO: Rename TAG_ALL to TAG_ANY.
     public static final int TAG_ALL = -1;
-    /** {@link #set} value for all sets combined, not including debug sets. */
+    /**
+     * {@link #set} value for all sets combined, not including debug sets.
+     * @hide
+     */
     public static final int SET_ALL = -1;
     /** {@link #set} value where background data is accounted. */
     public static final int SET_DEFAULT = 0;
     /** {@link #set} value where foreground data is accounted. */
     public static final int SET_FOREGROUND = 1;
-    /** All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values. */
+    /**
+     * All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values.
+     * @hide
+     */
     public static final int SET_DEBUG_START = 1000;
-    /** Debug {@link #set} value when the VPN stats are moved in. */
+    /**
+     * Debug {@link #set} value when the VPN stats are moved in.
+     * @hide
+     */
     public static final int SET_DBG_VPN_IN = 1001;
-    /** Debug {@link #set} value when the VPN stats are moved out of a vpn UID. */
+    /**
+     * Debug {@link #set} value when the VPN stats are moved out of a vpn UID.
+     * @hide
+     */
     public static final int SET_DBG_VPN_OUT = 1002;
 
-    /** Include all interfaces when filtering */
-    public static final String[] INTERFACES_ALL = null;
+    /**
+     * Include all interfaces when filtering
+     * @hide
+     */
+    public @Nullable static final String[] INTERFACES_ALL = null;
 
     /** {@link #tag} value for total data across all tags. */
     // TODO: Rename TAG_NONE to TAG_ALL.
     public static final int TAG_NONE = 0;
 
-    /** {@link #metered} value to account for all metered states. */
+    /**
+     * {@link #metered} value to account for all metered states.
+     * @hide
+     */
     public static final int METERED_ALL = -1;
     /** {@link #metered} value where native, unmetered data is accounted. */
     public static final int METERED_NO = 0;
     /** {@link #metered} value where metered data is accounted. */
     public static final int METERED_YES = 1;
 
-    /** {@link #roaming} value to account for all roaming states. */
+    /**
+     * {@link #roaming} value to account for all roaming states.
+     * @hide
+     */
     public static final int ROAMING_ALL = -1;
     /** {@link #roaming} value where native, non-roaming data is accounted. */
     public static final int ROAMING_NO = 0;
     /** {@link #roaming} value where roaming data is accounted. */
     public static final int ROAMING_YES = 1;
 
-    /** {@link #onDefaultNetwork} value to account for all default network states. */
+    /**
+     * {@link #onDefaultNetwork} value to account for all default network states.
+     * @hide
+     */
     public static final int DEFAULT_NETWORK_ALL = -1;
     /** {@link #onDefaultNetwork} value to account for usage while not the default network. */
     public static final int DEFAULT_NETWORK_NO = 0;
     /** {@link #onDefaultNetwork} value to account for usage while the default network. */
     public static final int DEFAULT_NETWORK_YES = 1;
 
-    /** Denotes a request for stats at the interface level. */
+    /**
+     * Denotes a request for stats at the interface level.
+     * @hide
+     */
     public static final int STATS_PER_IFACE = 0;
-    /** Denotes a request for stats at the interface and UID level. */
+    /**
+     * Denotes a request for stats at the interface and UID level.
+     * @hide
+     */
     public static final int STATS_PER_UID = 1;
 
     private static final String CLATD_INTERFACE_PREFIX = "v4-";
@@ -144,60 +192,78 @@
     @UnsupportedAppUsage
     private long[] operations;
 
+    /** @hide */
+    @SystemApi
     public static class Entry {
+        /** @hide */
         @UnsupportedAppUsage
         public String iface;
+        /** @hide */
         @UnsupportedAppUsage
         public int uid;
+        /** @hide */
         @UnsupportedAppUsage
         public int set;
+        /** @hide */
         @UnsupportedAppUsage
         public int tag;
         /**
          * Note that this is only populated w/ the default value when read from /proc or written
          * to disk. We merge in the correct value when reporting this value to clients of
          * getSummary().
+         * @hide
          */
         public int metered;
         /**
          * Note that this is only populated w/ the default value when read from /proc or written
          * to disk. We merge in the correct value when reporting this value to clients of
          * getSummary().
+         * @hide
          */
         public int roaming;
         /**
          * Note that this is only populated w/ the default value when read from /proc or written
          * to disk. We merge in the correct value when reporting this value to clients of
          * getSummary().
+         * @hide
          */
         public int defaultNetwork;
+        /** @hide */
         @UnsupportedAppUsage
         public long rxBytes;
+        /** @hide */
         @UnsupportedAppUsage
         public long rxPackets;
+        /** @hide */
         @UnsupportedAppUsage
         public long txBytes;
+        /** @hide */
         @UnsupportedAppUsage
         public long txPackets;
+        /** @hide */
+        @UnsupportedAppUsage
         public long operations;
 
+        /** @hide */
         @UnsupportedAppUsage
         public Entry() {
             this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
         }
 
+        /** @hide */
         public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
             this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets,
                     operations);
         }
 
+        /** @hide */
         public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets,
                 long txBytes, long txPackets, long operations) {
             this(iface, uid, set, tag, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
                     rxBytes, rxPackets, txBytes, txPackets, operations);
         }
 
-        public Entry(String iface, int uid, int set, int tag, int metered, int roaming,
+        public Entry(@Nullable String iface, int uid, int set, int tag, int metered, int roaming,
                  int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets,
                  long operations) {
             this.iface = iface;
@@ -214,15 +280,18 @@
             this.operations = operations;
         }
 
+        /** @hide */
         public boolean isNegative() {
             return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0;
         }
 
+        /** @hide */
         public boolean isEmpty() {
             return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0
                     && operations == 0;
         }
 
+        /** @hide */
         public void add(Entry another) {
             this.rxBytes += another.rxBytes;
             this.rxPackets += another.rxPackets;
@@ -249,6 +318,7 @@
             return builder.toString();
         }
 
+        /** @hide */
         @Override
         public boolean equals(Object o) {
             if (o instanceof Entry) {
@@ -262,13 +332,13 @@
             return false;
         }
 
+        /** @hide */
         @Override
         public int hashCode() {
             return Objects.hash(uid, set, tag, metered, roaming, defaultNetwork, iface);
         }
     }
 
-    @UnsupportedAppUsage
     public NetworkStats(long elapsedRealtime, int initialSize) {
         this.elapsedRealtime = elapsedRealtime;
         this.size = 0;
@@ -292,6 +362,7 @@
         }
     }
 
+    /** @hide */
     @UnsupportedAppUsage
     public NetworkStats(Parcel parcel) {
         elapsedRealtime = parcel.readLong();
@@ -312,7 +383,7 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeLong(elapsedRealtime);
         dest.writeInt(size);
         dest.writeInt(capacity);
@@ -330,19 +401,23 @@
         dest.writeLongArray(operations);
     }
 
+    /**
+     * @hide
+     */
     @Override
     public NetworkStats clone() {
         final NetworkStats clone = new NetworkStats(elapsedRealtime, size);
         NetworkStats.Entry entry = null;
         for (int i = 0; i < size; i++) {
             entry = getValues(i, entry);
-            clone.addValues(entry);
+            clone.addEntry(entry);
         }
         return clone;
     }
 
     /**
      * Clear all data stored in this object.
+     * @hide
      */
     public void clear() {
         this.capacity = 0;
@@ -360,25 +435,28 @@
         this.operations = EmptyArray.LONG;
     }
 
+    /** @hide */
     @VisibleForTesting
     public NetworkStats addIfaceValues(
             String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
-        return addValues(
+        return addEntry(
                 iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L);
     }
 
+    /** @hide */
     @VisibleForTesting
-    public NetworkStats addValues(String iface, int uid, int set, int tag, long rxBytes,
+    public NetworkStats addEntry(String iface, int uid, int set, int tag, long rxBytes,
             long rxPackets, long txBytes, long txPackets, long operations) {
-        return addValues(new Entry(
+        return addEntry(new Entry(
                 iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
     }
 
+    /** @hide */
     @VisibleForTesting
-    public NetworkStats addValues(String iface, int uid, int set, int tag, int metered, int roaming,
+    public NetworkStats addEntry(String iface, int uid, int set, int tag, int metered, int roaming,
             int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets,
             long operations) {
-        return addValues(new Entry(
+        return addEntry(new Entry(
                 iface, uid, set, tag, metered, roaming, defaultNetwork, rxBytes, rxPackets,
                 txBytes, txPackets, operations));
     }
@@ -386,8 +464,9 @@
     /**
      * Add new stats entry, copying from given {@link Entry}. The {@link Entry}
      * object can be recycled across multiple calls.
+     * @hide
      */
-    public NetworkStats addValues(Entry entry) {
+    public NetworkStats addEntry(Entry entry) {
         if (size >= capacity) {
             final int newLength = Math.max(size, 10) * 3 / 2;
             iface = Arrays.copyOf(iface, newLength);
@@ -428,6 +507,7 @@
 
     /**
      * Return specific stats entry.
+     * @hide
      */
     @UnsupportedAppUsage
     public Entry getValues(int i, Entry recycle) {
@@ -467,10 +547,12 @@
         operations[dest] = operations[src];
     }
 
+    /** @hide */
     public long getElapsedRealtime() {
         return elapsedRealtime;
     }
 
+    /** @hide */
     public void setElapsedRealtime(long time) {
         elapsedRealtime = time;
     }
@@ -478,21 +560,25 @@
     /**
      * Return age of this {@link NetworkStats} object with respect to
      * {@link SystemClock#elapsedRealtime()}.
+     * @hide
      */
     public long getElapsedRealtimeAge() {
         return SystemClock.elapsedRealtime() - elapsedRealtime;
     }
 
+    /** @hide */
     @UnsupportedAppUsage
     public int size() {
         return size;
     }
 
+    /** @hide */
     @VisibleForTesting
     public int internalSize() {
         return capacity;
     }
 
+    /** @hide */
     @Deprecated
     public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
             long txBytes, long txPackets, long operations) {
@@ -501,6 +587,7 @@
                 txPackets, operations);
     }
 
+    /** @hide */
     public NetworkStats combineValues(String iface, int uid, int set, int tag,
             long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
         return combineValues(new Entry(
@@ -509,16 +596,20 @@
 
     /**
      * Combine given values with an existing row, or create a new row if
-     * {@link #findIndex(String, int, int, int, int)} is unable to find match. Can
-     * also be used to subtract values from existing rows.
+     * {@link #findIndex(String, int, int, int, int, int, int)} is unable to find match. Can
+     * also be used to subtract values from existing rows. This method mutates the referencing
+     * {@link NetworkStats} object.
+     *
+     * @param entry the {@link Entry} to combine.
+     * @return a reference to this mutated {@link NetworkStats} object.
+     * @hide
      */
-    @UnsupportedAppUsage
-    public NetworkStats combineValues(Entry entry) {
+    public @NonNull NetworkStats combineValues(@NonNull Entry entry) {
         final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag, entry.metered,
                 entry.roaming, entry.defaultNetwork);
         if (i == -1) {
             // only create new entry when positive contribution
-            addValues(entry);
+            addEntry(entry);
         } else {
             rxBytes[i] += entry.rxBytes;
             rxPackets[i] += entry.rxPackets;
@@ -530,10 +621,33 @@
     }
 
     /**
-     * Combine all values from another {@link NetworkStats} into this object.
+     * Add given values with an existing row, or create a new row if
+     * {@link #findIndex(String, int, int, int, int, int, int)} is unable to find match. Can
+     * also be used to subtract values from existing rows.
+     *
+     * @param entry the {@link Entry} to add.
+     * @return a new constructed {@link NetworkStats} object that contains the result.
      */
-    @UnsupportedAppUsage
-    public void combineAllValues(NetworkStats another) {
+    public @NonNull NetworkStats addValues(@NonNull Entry entry) {
+        return this.clone().combineValues(entry);
+    }
+
+    /**
+     * Add the given {@link NetworkStats} objects.
+     *
+     * @return the sum of two objects.
+     */
+    public @NonNull NetworkStats add(@NonNull NetworkStats another) {
+        final NetworkStats ret = this.clone();
+        ret.combineAllValues(another);
+        return ret;
+    }
+
+    /**
+     * Combine all values from another {@link NetworkStats} into this object.
+     * @hide
+     */
+    public void combineAllValues(@NonNull NetworkStats another) {
         NetworkStats.Entry entry = null;
         for (int i = 0; i < another.size; i++) {
             entry = another.getValues(i, entry);
@@ -543,6 +657,7 @@
 
     /**
      * Find first stats index that matches the requested parameters.
+     * @hide
      */
     public int findIndex(String iface, int uid, int set, int tag, int metered, int roaming,
             int defaultNetwork) {
@@ -560,6 +675,7 @@
     /**
      * Find first stats index that matches the requested parameters, starting
      * search around the hinted index as an optimization.
+     * @hide
      */
     @VisibleForTesting
     public int findIndexHinted(String iface, int uid, int set, int tag, int metered, int roaming,
@@ -589,6 +705,7 @@
      * Splice in {@link #operations} from the given {@link NetworkStats} based
      * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
      * since operation counts are at data layer.
+     * @hide
      */
     public void spliceOperationsFrom(NetworkStats stats) {
         for (int i = 0; i < size; i++) {
@@ -604,6 +721,7 @@
 
     /**
      * Return list of unique interfaces known by this data structure.
+     * @hide
      */
     public String[] getUniqueIfaces() {
         final HashSet<String> ifaces = new HashSet<String>();
@@ -617,6 +735,7 @@
 
     /**
      * Return list of unique UIDs known by this data structure.
+     * @hide
      */
     @UnsupportedAppUsage
     public int[] getUniqueUids() {
@@ -636,6 +755,7 @@
     /**
      * Return total bytes represented by this snapshot object, usually used when
      * checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
+     * @hide
      */
     @UnsupportedAppUsage
     public long getTotalBytes() {
@@ -645,6 +765,7 @@
 
     /**
      * Return total of all fields represented by this snapshot object.
+     * @hide
      */
     @UnsupportedAppUsage
     public Entry getTotal(Entry recycle) {
@@ -654,6 +775,7 @@
     /**
      * Return total of all fields represented by this snapshot object matching
      * the requested {@link #uid}.
+     * @hide
      */
     @UnsupportedAppUsage
     public Entry getTotal(Entry recycle, int limitUid) {
@@ -663,11 +785,13 @@
     /**
      * Return total of all fields represented by this snapshot object matching
      * the requested {@link #iface}.
+     * @hide
      */
     public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
         return getTotal(recycle, limitIface, UID_ALL, false);
     }
 
+    /** @hide */
     @UnsupportedAppUsage
     public Entry getTotalIncludingTags(Entry recycle) {
         return getTotal(recycle, null, UID_ALL, true);
@@ -717,6 +841,7 @@
 
     /**
      * Fast path for battery stats.
+     * @hide
      */
     public long getTotalPackets() {
         long total = 0;
@@ -729,9 +854,12 @@
     /**
      * Subtract the given {@link NetworkStats}, effectively leaving the delta
      * between two snapshots in time. Assumes that statistics rows collect over
-     * time, and that none of them have disappeared.
+     * time, and that none of them have disappeared. This method does not mutate
+     * the referencing object.
+     *
+     * @return the delta between two objects.
      */
-    public NetworkStats subtract(NetworkStats right) {
+    public @NonNull NetworkStats subtract(@NonNull NetworkStats right) {
         return subtract(this, right, null, null);
     }
 
@@ -742,6 +870,7 @@
      * <p>
      * If counters have rolled backwards, they are clamped to {@code 0} and
      * reported to the given {@link NonMonotonicObserver}.
+     * @hide
      */
     public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
             NonMonotonicObserver<C> observer, C cookie) {
@@ -759,6 +888,7 @@
      * If <var>recycle</var> is supplied, this NetworkStats object will be
      * reused (and returned) as the result if it is large enough to contain
      * the data.
+     * @hide
      */
     public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
             NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle) {
@@ -817,7 +947,7 @@
                 entry.operations = Math.max(entry.operations, 0);
             }
 
-            result.addValues(entry);
+            result.addEntry(entry);
         }
 
         return result;
@@ -847,6 +977,7 @@
      * @param stackedTraffic Stats with traffic stacked on top of our ifaces. Will also be mutated.
      * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
      * @param useBpfStats True if eBPF is in use.
+     * @hide
      */
     public static void apply464xlatAdjustments(NetworkStats baseTraffic,
             NetworkStats stackedTraffic, Map<String, String> stackedIfaces, boolean useBpfStats) {
@@ -899,6 +1030,7 @@
      * {@link #apply464xlatAdjustments(NetworkStats, NetworkStats, Map)} with {@code this} as
      * base and stacked traffic.
      * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
+     * @hide
      */
     public void apply464xlatAdjustments(Map<String, String> stackedIfaces, boolean useBpfStats) {
         apply464xlatAdjustments(this, this, stackedIfaces, useBpfStats);
@@ -907,6 +1039,7 @@
     /**
      * Return total statistics grouped by {@link #iface}; doesn't mutate the
      * original structure.
+     * @hide
      */
     public NetworkStats groupedByIface() {
         final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
@@ -938,6 +1071,7 @@
     /**
      * Return total statistics grouped by {@link #uid}; doesn't mutate the
      * original structure.
+     * @hide
      */
     public NetworkStats groupedByUid() {
         final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
@@ -968,6 +1102,7 @@
 
     /**
      * Remove all rows that match one of specified UIDs.
+     * @hide
      */
     public void removeUids(int[] uids) {
         int nextOutputEntry = 0;
@@ -989,6 +1124,7 @@
      * @param limitUid UID to filter for, or {@link #UID_ALL}.
      * @param limitIfaces Interfaces to filter for, or {@link #INTERFACES_ALL}.
      * @param limitTag Tag to filter for, or {@link #TAG_ALL}.
+     * @hide
      */
     public void filter(int limitUid, String[] limitIfaces, int limitTag) {
         if (limitUid == UID_ALL && limitTag == TAG_ALL && limitIfaces == INTERFACES_ALL) {
@@ -1004,6 +1140,7 @@
      * Only keep entries with {@link #set} value less than {@link #SET_DEBUG_START}.
      *
      * <p>This mutates the original structure in place.
+     * @hide
      */
     public void filterDebugEntries() {
         filter(e -> e.set < SET_DEBUG_START);
@@ -1024,6 +1161,7 @@
         size = nextOutputEntry;
     }
 
+    /** @hide */
     public void dump(String prefix, PrintWriter pw) {
         pw.print(prefix);
         pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
@@ -1047,6 +1185,7 @@
 
     /**
      * Return text description of {@link #set} value.
+     * @hide
      */
     public static String setToString(int set) {
         switch (set) {
@@ -1067,6 +1206,7 @@
 
     /**
      * Return text description of {@link #set} value.
+     * @hide
      */
     public static String setToCheckinString(int set) {
         switch (set) {
@@ -1087,6 +1227,7 @@
 
     /**
      * @return true if the querySet matches the dataSet.
+     * @hide
      */
     public static boolean setMatches(int querySet, int dataSet) {
         if (querySet == dataSet) {
@@ -1098,6 +1239,7 @@
 
     /**
      * Return text description of {@link #tag} value.
+     * @hide
      */
     public static String tagToString(int tag) {
         return "0x" + Integer.toHexString(tag);
@@ -1105,6 +1247,7 @@
 
     /**
      * Return text description of {@link #metered} value.
+     * @hide
      */
     public static String meteredToString(int metered) {
         switch (metered) {
@@ -1121,6 +1264,7 @@
 
     /**
      * Return text description of {@link #roaming} value.
+     * @hide
      */
     public static String roamingToString(int roaming) {
         switch (roaming) {
@@ -1137,6 +1281,7 @@
 
     /**
      * Return text description of {@link #defaultNetwork} value.
+     * @hide
      */
     public static String defaultNetworkToString(int defaultNetwork) {
         switch (defaultNetwork) {
@@ -1151,6 +1296,7 @@
         }
     }
 
+    /** @hide */
     @Override
     public String toString() {
         final CharArrayWriter writer = new CharArrayWriter();
@@ -1163,8 +1309,7 @@
         return 0;
     }
 
-    @UnsupportedAppUsage
-    public static final @android.annotation.NonNull Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
+    public static final @NonNull Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
         @Override
         public NetworkStats createFromParcel(Parcel in) {
             return new NetworkStats(in);
@@ -1176,6 +1321,7 @@
         }
     };
 
+    /** @hide */
     public interface NonMonotonicObserver<C> {
         public void foundNonMonotonic(
                 NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie);
@@ -1195,6 +1341,7 @@
      * @param tunUid uid of the VPN application
      * @param tunIface iface of the vpn tunnel
      * @param underlyingIfaces underlying network ifaces used by the VPN application
+     * @hide
      */
     public void migrateTun(int tunUid, @NonNull String tunIface,
             @NonNull String[] underlyingIfaces) {
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 162d6e2..8108cf0 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -89,7 +89,7 @@
      *
      * @hide
      */
-    public static final int UID_TETHERING = -5;
+    public static final int UID_TETHERING = NetworkStats.UID_TETHERING;
 
     /**
      * Tag values in this range are reserved for the network stack. The network stack is
diff --git a/core/java/android/net/annotations/PolicyDirection.java b/core/java/android/net/annotations/PolicyDirection.java
new file mode 100644
index 0000000..febd9b4
--- /dev/null
+++ b/core/java/android/net/annotations/PolicyDirection.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.annotations;
+
+import android.annotation.IntDef;
+import android.net.IpSecManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * IPsec traffic direction.
+ *
+ * <p>Mainline modules cannot reference hidden @IntDef. Moving this annotation to a separate class
+ * to allow others to statically include it.
+ *
+ * @hide
+ */
+@IntDef(value = {IpSecManager.DIRECTION_IN, IpSecManager.DIRECTION_OUT})
+@Retention(RetentionPolicy.SOURCE)
+public @interface PolicyDirection {}
diff --git a/core/java/android/net/netstats/provider/AbstractNetworkStatsProvider.java b/core/java/android/net/netstats/provider/AbstractNetworkStatsProvider.java
new file mode 100644
index 0000000..740aa92
--- /dev/null
+++ b/core/java/android/net/netstats/provider/AbstractNetworkStatsProvider.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netstats.provider;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.NetworkStats;
+
+/**
+ * A base class that allows external modules to implement a custom network statistics provider.
+ * @hide
+ */
+@SystemApi
+public abstract class AbstractNetworkStatsProvider {
+    /**
+     * A value used by {@link #setLimit} and {@link #setAlert} indicates there is no limit.
+     */
+    public static final int QUOTA_UNLIMITED = -1;
+
+    /**
+     * Called by {@code NetworkStatsService} when global polling is needed. Custom
+     * implementation of providers MUST respond to it by calling
+     * {@link NetworkStatsProviderCallback#onStatsUpdated} within one minute. Responding
+     * later than this may cause the stats to be dropped.
+     *
+     * @param token a positive number identifying the new state of the system under which
+     *              {@link NetworkStats} have to be gathered from now on. When this is called,
+     *              custom implementations of providers MUST report the latest stats with the
+     *              previous token, under which stats were being gathered so far.
+     */
+    public abstract void requestStatsUpdate(int token);
+
+    /**
+     * Called by {@code NetworkStatsService} when setting the interface quota for the specified
+     * upstream interface. When this is called, the custom implementation should block all egress
+     * packets on the {@code iface} associated with the provider when {@code quotaBytes} bytes have
+     * been reached, and MUST respond to it by calling
+     * {@link NetworkStatsProviderCallback#onLimitReached()}.
+     *
+     * @param iface the interface requiring the operation.
+     * @param quotaBytes the quota defined as the number of bytes, starting from zero and counting
+     *                   from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit.
+     */
+    public abstract void setLimit(@NonNull String iface, long quotaBytes);
+
+    /**
+     * Called by {@code NetworkStatsService} when setting the alert bytes. Custom implementations
+     * MUST call {@link NetworkStatsProviderCallback#onAlertReached()} when {@code quotaBytes} bytes
+     * have been reached. Unlike {@link #setLimit(String, long)}, the custom implementation should
+     * not block all egress packets.
+     *
+     * @param quotaBytes the quota defined as the number of bytes, starting from zero and counting
+     *                   from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no alert.
+     */
+    public abstract void setAlert(long quotaBytes);
+}
diff --git a/core/java/android/net/NetworkMisc.aidl b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
similarity index 60%
copy from core/java/android/net/NetworkMisc.aidl
copy to core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
index c65583f..55b3d4e 100644
--- a/core/java/android/net/NetworkMisc.aidl
+++ b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,15 @@
  * limitations under the License.
  */
 
-package android.net;
+package android.net.netstats.provider;
 
-parcelable NetworkMisc;
+/**
+ * Interface for NetworkStatsService to query network statistics and set data limits.
+ *
+ * @hide
+ */
+oneway interface INetworkStatsProvider {
+    void requestStatsUpdate(int token);
+    void setLimit(String iface, long quotaBytes);
+    void setAlert(long quotaBytes);
+}
diff --git a/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
new file mode 100644
index 0000000..3ea9318
--- /dev/null
+++ b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netstats.provider;
+
+import android.net.NetworkStats;
+
+/**
+ * Interface for implementor of {@link INetworkStatsProviderCallback} to push events
+ * such as network statistics update or notify limit reached.
+ * @hide
+ */
+oneway interface INetworkStatsProviderCallback {
+    void onStatsUpdated(int token, in NetworkStats ifaceStats, in NetworkStats uidStats);
+    void onAlertReached();
+    void onLimitReached();
+    void unregister();
+}
diff --git a/core/java/android/net/netstats/provider/NetworkStatsProviderCallback.java b/core/java/android/net/netstats/provider/NetworkStatsProviderCallback.java
new file mode 100644
index 0000000..e17a8ee
--- /dev/null
+++ b/core/java/android/net/netstats/provider/NetworkStatsProviderCallback.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netstats.provider;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.net.NetworkStats;
+import android.os.RemoteException;
+
+/**
+ * A callback class that allows callers to report events to the system.
+ * @hide
+ */
+@SystemApi
+@SuppressLint("CallbackMethodName")
+public class NetworkStatsProviderCallback {
+    @NonNull private final INetworkStatsProviderCallback mBinder;
+
+    /** @hide */
+    public NetworkStatsProviderCallback(@NonNull INetworkStatsProviderCallback binder) {
+        mBinder = binder;
+    }
+
+    /**
+     * Notify the system of new network statistics.
+     *
+     * Send the network statistics recorded since the last call to {@link #onStatsUpdated}. Must be
+     * called within one minute of {@link AbstractNetworkStatsProvider#requestStatsUpdate(int)}
+     * being called. The provider can also call this whenever it wants to reports new stats for any
+     * reason. Note that the system will not necessarily immediately propagate the statistics to
+     * reflect the update.
+     *
+     * @param token the token under which these stats were gathered. Providers can call this method
+     *              with the current token as often as they want, until the token changes.
+     *              {@see AbstractNetworkStatsProvider#requestStatsUpdate()}
+     * @param ifaceStats the {@link NetworkStats} per interface to be reported.
+     *                   The provider should not include any traffic that is already counted by
+     *                   kernel interface counters.
+     * @param uidStats the same stats as above, but counts {@link NetworkStats}
+     *                 per uid.
+     */
+    public void onStatsUpdated(int token, @NonNull NetworkStats ifaceStats,
+            @NonNull NetworkStats uidStats) {
+        try {
+            mBinder.onStatsUpdated(token, ifaceStats, uidStats);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Notify system that the quota set by {@code setAlert} has been reached.
+     */
+    public void onAlertReached() {
+        try {
+            mBinder.onAlertReached();
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Notify system that the quota set by {@code setLimit} has been reached.
+     */
+    public void onLimitReached() {
+        try {
+            mBinder.onLimitReached();
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Unregister the provider and the referencing callback.
+     */
+    public void unregister() {
+        try {
+            mBinder.unregister();
+        } catch (RemoteException e) {
+            // Ignore error.
+        }
+    }
+}
diff --git a/core/java/android/net/netstats/provider/NetworkStatsProviderWrapper.java b/core/java/android/net/netstats/provider/NetworkStatsProviderWrapper.java
new file mode 100644
index 0000000..4bf7c9b
--- /dev/null
+++ b/core/java/android/net/netstats/provider/NetworkStatsProviderWrapper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netstats.provider;
+
+import android.annotation.NonNull;
+
+/**
+ * A wrapper class of {@link INetworkStatsProvider} that hides the binder interface from exposing
+ * to outer world.
+ *
+ * @hide
+ */
+public class NetworkStatsProviderWrapper extends INetworkStatsProvider.Stub {
+    @NonNull final AbstractNetworkStatsProvider mProvider;
+
+    public NetworkStatsProviderWrapper(AbstractNetworkStatsProvider provider) {
+        mProvider = provider;
+    }
+
+    @Override
+    public void requestStatsUpdate(int token) {
+        mProvider.requestStatsUpdate(token);
+    }
+
+    @Override
+    public void setLimit(@NonNull String iface, long quotaBytes) {
+        mProvider.setLimit(iface, quotaBytes);
+    }
+
+    @Override
+    public void setAlert(long quotaBytes) {
+        mProvider.setAlert(quotaBytes);
+    }
+}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index c9ebed3..1a4dac7 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1886,6 +1886,43 @@
     public final void writeException(@NonNull Exception e) {
         AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
 
+        int code = getExceptionCode(e);
+        writeInt(code);
+        StrictMode.clearGatheredViolations();
+        if (code == 0) {
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            }
+            throw new RuntimeException(e);
+        }
+        writeString(e.getMessage());
+        final long timeNow = sParcelExceptionStackTrace ? SystemClock.elapsedRealtime() : 0;
+        if (sParcelExceptionStackTrace && (timeNow - sLastWriteExceptionStackTrace
+                > WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS)) {
+            sLastWriteExceptionStackTrace = timeNow;
+            writeStackTrace(e);
+        } else {
+            writeInt(0);
+        }
+        switch (code) {
+            case EX_SERVICE_SPECIFIC:
+                writeInt(((ServiceSpecificException) e).errorCode);
+                break;
+            case EX_PARCELABLE:
+                // Write parceled exception prefixed by length
+                final int sizePosition = dataPosition();
+                writeInt(0);
+                writeParcelable((Parcelable) e, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+                final int payloadPosition = dataPosition();
+                setDataPosition(sizePosition);
+                writeInt(payloadPosition - sizePosition);
+                setDataPosition(payloadPosition);
+                break;
+        }
+    }
+
+    /** @hide */
+    public static int getExceptionCode(@NonNull Throwable e) {
         int code = 0;
         if (e instanceof Parcelable
                 && (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) {
@@ -1909,51 +1946,25 @@
         } else if (e instanceof ServiceSpecificException) {
             code = EX_SERVICE_SPECIFIC;
         }
-        writeInt(code);
-        StrictMode.clearGatheredViolations();
-        if (code == 0) {
-            if (e instanceof RuntimeException) {
-                throw (RuntimeException) e;
-            }
-            throw new RuntimeException(e);
+        return code;
+    }
+
+    /** @hide */
+    public void writeStackTrace(@NonNull Throwable e) {
+        final int sizePosition = dataPosition();
+        writeInt(0); // Header size will be filled in later
+        StackTraceElement[] stackTrace = e.getStackTrace();
+        final int truncatedSize = Math.min(stackTrace.length, 5);
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < truncatedSize; i++) {
+            sb.append("\tat ").append(stackTrace[i]).append('\n');
         }
-        writeString(e.getMessage());
-        final long timeNow = sParcelExceptionStackTrace ? SystemClock.elapsedRealtime() : 0;
-        if (sParcelExceptionStackTrace && (timeNow - sLastWriteExceptionStackTrace
-                > WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS)) {
-            sLastWriteExceptionStackTrace = timeNow;
-            final int sizePosition = dataPosition();
-            writeInt(0); // Header size will be filled in later
-            StackTraceElement[] stackTrace = e.getStackTrace();
-            final int truncatedSize = Math.min(stackTrace.length, 5);
-            StringBuilder sb = new StringBuilder();
-            for (int i = 0; i < truncatedSize; i++) {
-                sb.append("\tat ").append(stackTrace[i]).append('\n');
-            }
-            writeString(sb.toString());
-            final int payloadPosition = dataPosition();
-            setDataPosition(sizePosition);
-            // Write stack trace header size. Used in native side to skip the header
-            writeInt(payloadPosition - sizePosition);
-            setDataPosition(payloadPosition);
-        } else {
-            writeInt(0);
-        }
-        switch (code) {
-            case EX_SERVICE_SPECIFIC:
-                writeInt(((ServiceSpecificException) e).errorCode);
-                break;
-            case EX_PARCELABLE:
-                // Write parceled exception prefixed by length
-                final int sizePosition = dataPosition();
-                writeInt(0);
-                writeParcelable((Parcelable) e, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
-                final int payloadPosition = dataPosition();
-                setDataPosition(sizePosition);
-                writeInt(payloadPosition - sizePosition);
-                setDataPosition(payloadPosition);
-                break;
-        }
+        writeString(sb.toString());
+        final int payloadPosition = dataPosition();
+        setDataPosition(sizePosition);
+        // Write stack trace header size. Used in native side to skip the header
+        writeInt(payloadPosition - sizePosition);
+        setDataPosition(payloadPosition);
     }
 
     /**
@@ -2069,14 +2080,7 @@
         if (remoteStackTrace != null) {
             RemoteException cause = new RemoteException(
                     "Remote stack trace:\n" + remoteStackTrace, null, false, false);
-            try {
-                Throwable rootCause = ExceptionUtils.getRootCause(e);
-                if (rootCause != null) {
-                    rootCause.initCause(cause);
-                }
-            } catch (RuntimeException ex) {
-                Log.e(TAG, "Cannot set cause " + cause + " for " + e, ex);
-            }
+            ExceptionUtils.appendCause(e, cause);
         }
         SneakyThrow.sneakyThrow(e);
     }
@@ -2088,6 +2092,14 @@
      * @param msg The exception message.
      */
     private Exception createException(int code, String msg) {
+        Exception exception = createExceptionOrNull(code, msg);
+        return exception != null
+                ? exception
+                : new RuntimeException("Unknown exception code: " + code + " msg " + msg);
+    }
+
+    /** @hide */
+    public Exception createExceptionOrNull(int code, String msg) {
         switch (code) {
             case EX_PARCELABLE:
                 if (readInt() > 0) {
@@ -2111,9 +2123,9 @@
                 return new UnsupportedOperationException(msg);
             case EX_SERVICE_SPECIFIC:
                 return new ServiceSpecificException(readInt(), msg);
+            default:
+                return null;
         }
-        return new RuntimeException("Unknown exception code: " + code
-                + " msg " + msg);
     }
 
     /**
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 983053b..89ddf8c 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -31,6 +31,9 @@
 import static android.system.OsConstants.S_ISREG;
 import static android.system.OsConstants.S_IWOTH;
 
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
@@ -253,6 +256,9 @@
      *             be opened with the requested mode.
      * @see #parseMode(String)
      */
+    // We can't accept a generic Executor here, since we need to use
+    // MessageQueue.addOnFileDescriptorEventListener()
+    @SuppressLint("ExecutorRegistration")
     public static ParcelFileDescriptor open(File file, int mode, Handler handler,
             final OnCloseListener listener) throws IOException {
         if (handler == null) {
@@ -268,9 +274,22 @@
         return fromFd(fd, handler, listener);
     }
 
-    /** {@hide} */
-    public static ParcelFileDescriptor fromPfd(ParcelFileDescriptor pfd, Handler handler,
-            final OnCloseListener listener) throws IOException {
+    /**
+     * Create a new ParcelFileDescriptor wrapping an already-opened file.
+     *
+     * @param pfd The already-opened file.
+     * @param handler to call listener from.
+     * @param listener to be invoked when the returned descriptor has been
+     *            closed.
+     * @return a new ParcelFileDescriptor pointing to the given file.
+     * @hide
+     */
+    @SystemApi
+    // We can't accept a generic Executor here, since we need to use
+    // MessageQueue.addOnFileDescriptorEventListener()
+    @SuppressLint("ExecutorRegistration")
+    public static @NonNull ParcelFileDescriptor wrap(@NonNull ParcelFileDescriptor pfd,
+            @NonNull Handler handler, @NonNull OnCloseListener listener) throws IOException {
         final FileDescriptor original = new FileDescriptor();
         original.setInt$(pfd.detachFd());
         return fromFd(original, handler, listener);
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index b40283f..7a837e1 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -16,17 +16,24 @@
 
 package android.os;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.util.ArrayMap;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.ArrayList;
 
 /**
@@ -339,4 +346,44 @@
 
         proto.end(token);
     }
+
+    /**
+     * Writes the content of the {@link PersistableBundle} to a {@link OutputStream}.
+     *
+     * <p>The content can be read by a {@link #readFromStream}.
+     *
+     * @see #readFromStream
+     */
+    public void writeToStream(@NonNull OutputStream outputStream) throws IOException {
+        FastXmlSerializer serializer = new FastXmlSerializer();
+        serializer.setOutput(outputStream, UTF_8.name());
+        serializer.startTag(null, "bundle");
+        try {
+            saveToXml(serializer);
+        } catch (XmlPullParserException e) {
+            throw new IOException(e);
+        }
+        serializer.endTag(null, "bundle");
+        serializer.flush();
+    }
+
+    /**
+     * Reads a {@link PersistableBundle} from an {@link InputStream}.
+     *
+     * <p>The stream must be generated by {@link #writeToStream}.
+     *
+     * @see #writeToStream
+     */
+    @NonNull
+    public static PersistableBundle readFromStream(@NonNull InputStream inputStream)
+            throws IOException {
+        try {
+            XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+            parser.setInput(inputStream, UTF_8.name());
+            parser.next();
+            return PersistableBundle.restoreFromXml(parser);
+        } catch (XmlPullParserException e) {
+            throw new IOException(e);
+        }
+    }
 }
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 94623bc..5d80ab6 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -68,10 +68,9 @@
     public static final int LOG_UID = 1007;
 
     /**
-     * Defines the UID/GID for the WIFI supplicant process.
-     * @hide
+     * Defines the UID/GID for the WIFI native processes like wificond, supplicant, hostapd,
+     * vendor HAL, etc.
      */
-    @UnsupportedAppUsage
     public static final int WIFI_UID = 1010;
 
     /**
@@ -89,6 +88,12 @@
     public static final int DRM_UID = 1019;
 
     /**
+     * Defines the GID for the group that allows write access to the internal media storage.
+     * @hide
+     */
+    public static final int SDCARD_RW_GID = 1015;
+
+    /**
      * Defines the UID/GID for the group that controls VPN services.
      * @hide
      */
@@ -752,11 +757,12 @@
 
     /**
      * Set the priority of a thread, based on Linux priorities.
-     * 
-     * @param tid The identifier of the thread/process to change.
+     *
+     * @param tid The identifier of the thread/process to change. It should be
+     * the native thread id but not the managed id of {@link java.lang.Thread}.
      * @param priority A Linux priority level, from -20 for highest scheduling
      * priority to 19 for lowest scheduling priority.
-     * 
+     *
      * @throws IllegalArgumentException Throws IllegalArgumentException if
      * <var>tid</var> does not exist.
      * @throws SecurityException Throws SecurityException if your process does
diff --git a/core/java/android/os/TelephonyServiceManager.java b/core/java/android/os/TelephonyServiceManager.java
index 1211dd6..064cf7d 100644
--- a/core/java/android/os/TelephonyServiceManager.java
+++ b/core/java/android/os/TelephonyServiceManager.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.content.Context;
 
 /**
  * Provides a way to register and obtain the system service binder objects managed by the telephony
@@ -51,8 +52,8 @@
         /**
          * Register a system server binding object for a service.
          */
-        public void register(@NonNull IBinder binder) {
-            ServiceManager.addService(mServiceName, binder);
+        public void register(@NonNull IBinder service) {
+            ServiceManager.addService(mServiceName, service);
         }
 
         /**
@@ -114,25 +115,123 @@
      */
     @NonNull
     public ServiceRegisterer getTelephonyServiceRegisterer() {
-        return new ServiceRegisterer("phone");
+        return new ServiceRegisterer(Context.TELEPHONY_SERVICE);
     }
 
+    /**
+     * Returns {@link ServiceRegisterer} for the telephony registry service.
+     */
+    @NonNull
+    public ServiceRegisterer getTelephonyRegistryServiceRegisterer() {
+        return new ServiceRegisterer("telephony.registry");
+    }
 
-// TODO: Add more services...
-//
-//    /**
-//     * Returns {@link ServiceRegisterer} for the "subscription" service.
-//     */
-//    @NonNull
-//    public ServiceRegisterer getSubscriptionServiceRegisterer() {
-//        return new ServiceRegisterer("isub");
-//    }
-//
-//    /**
-//     * Returns {@link ServiceRegisterer} for the "SMS" service.
-//     */
-//    @NonNull
-//    public ServiceRegisterer getSmsServiceRegisterer() {
-//        return new ServiceRegisterer("isms");
-//    }
+    /**
+     * Returns {@link ServiceRegisterer} for the telephony IMS service.
+     */
+    @NonNull
+    public ServiceRegisterer getTelephonyImsServiceRegisterer() {
+        return new ServiceRegisterer(Context.TELEPHONY_IMS_SERVICE);
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the telephony RCS message service.
+     */
+    @NonNull
+    public ServiceRegisterer getTelephonyRcsMessageServiceRegisterer() {
+        return new ServiceRegisterer(Context.TELEPHONY_RCS_MESSAGE_SERVICE);
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the subscription service.
+     */
+    @NonNull
+    public ServiceRegisterer getSubscriptionServiceRegisterer() {
+        return new ServiceRegisterer("isub");
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the network policy service.
+     */
+    @NonNull
+    public ServiceRegisterer getNetworkPolicyServiceRegisterer() {
+        return new ServiceRegisterer(Context.NETWORK_POLICY_SERVICE);
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the phone sub service.
+     */
+    @NonNull
+    public ServiceRegisterer getPhoneSubServiceRegisterer() {
+        return new ServiceRegisterer("iphonesubinfo");
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the opportunistic network service.
+     */
+    @NonNull
+    public ServiceRegisterer getOpportunisticNetworkServiceRegisterer() {
+        return new ServiceRegisterer("ions");
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the carrier config service.
+     */
+    @NonNull
+    public ServiceRegisterer getCarrierConfigServiceRegisterer() {
+        return new ServiceRegisterer(Context.CARRIER_CONFIG_SERVICE);
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the "SMS" service.
+     */
+    @NonNull
+    public ServiceRegisterer getSmsServiceRegisterer() {
+        return new ServiceRegisterer("isms");
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the eUICC controller service.
+     */
+    @NonNull
+    public ServiceRegisterer getEuiccControllerService() {
+        return new ServiceRegisterer("econtroller");
+    }
+
+    @NonNull
+    public ServiceRegisterer getEuiccCardControllerServiceRegisterer() {
+        return new ServiceRegisterer("euicc_card_controller");
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the package manager service.
+     */
+    @NonNull
+    public ServiceRegisterer getPackageManagerServiceRegisterer() {
+        return new ServiceRegisterer("package");
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the permission manager service.
+     */
+    @NonNull
+    public ServiceRegisterer getPermissionManagerServiceRegisterer() {
+        return new ServiceRegisterer("permissionmgr");
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the ICC phone book service.
+     */
+    @NonNull
+    public ServiceRegisterer getIccPhoneBookServiceRegisterer() {
+        return new ServiceRegisterer("simphonebook");
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the window service.
+     */
+    @NonNull
+    public ServiceRegisterer getWindowServiceRegisterer() {
+        return new ServiceRegisterer(Context.WINDOW_SERVICE);
+    }
 }
diff --git a/core/java/android/util/TimestampedValue.java b/core/java/android/os/TimestampedValue.java
similarity index 97%
rename from core/java/android/util/TimestampedValue.java
rename to core/java/android/os/TimestampedValue.java
index 4505673..348574e 100644
--- a/core/java/android/util/TimestampedValue.java
+++ b/core/java/android/os/TimestampedValue.java
@@ -14,13 +14,10 @@
  * limitations under the License.
  */
 
-package android.util;
+package android.os;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.SystemClock;
 
 import java.util.Objects;
 
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index a9ddffe..73e1adf 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -16,8 +16,10 @@
 
 package android.os;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.content.res.AssetFileDescriptor;
 import android.os.IUpdateEngine;
 import android.os.IUpdateEngineCallback;
 import android.os.RemoteException;
@@ -140,8 +142,43 @@
          * {@code SWITCH_SLOT_ON_REBOOT=0}. See {@link #applyPayload}.
          */
         public static final int UPDATED_BUT_NOT_ACTIVE = 52;
+
+        /**
+         * Error code: there is not enough space on the device to apply the update. User should
+         * be prompted to free up space and re-try the update.
+         *
+         * <p>See {@link UpdateEngine#allocateSpace}.
+         */
+        public static final int NOT_ENOUGH_SPACE = 60;
+
+        /**
+         * Error code: the device is corrupted and no further updates may be applied.
+         *
+         * <p>See {@link UpdateEngine#cleanupAppliedPayload}.
+         */
+        public static final int DEVICE_CORRUPTED = 61;
     }
 
+    /** @hide */
+    @IntDef(value = {
+            ErrorCodeConstants.SUCCESS,
+            ErrorCodeConstants.ERROR,
+            ErrorCodeConstants.FILESYSTEM_COPIER_ERROR,
+            ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR,
+            ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR,
+            ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR,
+            ErrorCodeConstants.KERNEL_DEVICE_OPEN_ERROR,
+            ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR,
+            ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR,
+            ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR,
+            ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR,
+            ErrorCodeConstants.PAYLOAD_TIMESTAMP_ERROR,
+            ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE,
+            ErrorCodeConstants.NOT_ENOUGH_SPACE,
+            ErrorCodeConstants.DEVICE_CORRUPTED,
+    })
+    public @interface ErrorCode {}
+
     /**
      * Status codes for update engine. Values must agree with the ones in
      * {@code system/update_engine/client_library/include/update_engine/update_status.h}.
@@ -313,16 +350,17 @@
     }
 
     /**
-     * Applies the payload passed as ParcelFileDescriptor {@code pfd} instead of
-     * using the {@code file://} scheme.
+     * Applies the payload passed as AssetFileDescriptor {@code assetFd}
+     * instead of using the {@code file://} scheme.
      *
      * <p>See {@link #applyPayload(String)} for {@code offset}, {@code size} and
      * {@code headerKeyValuePairs} parameters.
      */
-    public void applyPayload(@NonNull ParcelFileDescriptor pfd, long offset, long size,
+    public void applyPayload(@NonNull AssetFileDescriptor assetFd,
             @NonNull String[] headerKeyValuePairs) {
         try {
-            mUpdateEngine.applyPayloadFd(pfd, offset, size, headerKeyValuePairs);
+            mUpdateEngine.applyPayloadFd(assetFd.getParcelFileDescriptor(),
+                    assetFd.getStartOffset(), assetFd.getLength(), headerKeyValuePairs);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -419,4 +457,138 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Return value of {@link #allocateSpace.}
+     */
+    public static final class AllocateSpaceResult {
+        private @ErrorCode int mErrorCode = ErrorCodeConstants.SUCCESS;
+        private long mFreeSpaceRequired = 0;
+        private AllocateSpaceResult() {}
+        /**
+         * Error code.
+         *
+         * @return The following error codes:
+         * <ul>
+         * <li>{@link ErrorCodeConstants#SUCCESS} if space has been allocated
+         *         successfully.</li>
+         * <li>{@link ErrorCodeConstants#NOT_ENOUGH_SPACE} if insufficient
+         *         space.</li>
+         * <li>Other {@link ErrorCodeConstants} for other errors.</li>
+         * </ul>
+         */
+        @ErrorCode
+        public int errorCode() {
+            return mErrorCode;
+        }
+
+        /**
+         * Estimated total space that needs to be available on the userdata partition to apply the
+         * payload (in bytes).
+         *
+         * <p>
+         * Note that in practice, more space needs to be made available before applying the payload
+         * to keep the device working.
+         *
+         * @return The following values:
+         * <ul>
+         * <li>zero if {@link #errorCode} returns {@link ErrorCodeConstants#SUCCESS}</li>
+         * <li>non-zero if {@link #errorCode} returns {@link ErrorCodeConstants#NOT_ENOUGH_SPACE}.
+         * Value is the estimated total space required on userdata partition.</li>
+         * </ul>
+         * @throws IllegalStateException if {@link #errorCode} is not one of the above.
+         *
+         */
+        public long freeSpaceRequired() {
+            if (mErrorCode == ErrorCodeConstants.SUCCESS) {
+                return 0;
+            }
+            if (mErrorCode == ErrorCodeConstants.NOT_ENOUGH_SPACE) {
+                return mFreeSpaceRequired;
+            }
+            throw new IllegalStateException(String.format(
+                    "freeSpaceRequired() is not available when error code is %d", mErrorCode));
+        }
+    }
+
+    /**
+     * Initialize partitions for a payload associated with the given payload
+     * metadata {@code payloadMetadataFilename} by preallocating required space.
+     *
+     * <p>This function should be called after payload has been verified after
+     * {@link #verifyPayloadMetadata}. This function does not verify whether
+     * the given payload is applicable or not.
+     *
+     * <p>Implementation of {@code allocateSpace} uses
+     * {@code headerKeyValuePairs} to determine whether space has been allocated
+     * for a different or same payload previously. If space has been allocated
+     * for a different payload before, space will be reallocated for the given
+     * payload. If space has been allocated for the same payload, no actions to
+     * storage devices are taken.
+     *
+     * <p>This function is synchronous and may take a non-trivial amount of
+     * time. Callers should call this function in a background thread.
+     *
+     * @param payloadMetadataFilename See {@link #verifyPayloadMetadata}.
+     * @param headerKeyValuePairs See {@link #applyPayload}.
+     * @return See {@link AllocateSpaceResult}.
+     */
+    @NonNull
+    public AllocateSpaceResult allocateSpace(
+                @NonNull String payloadMetadataFilename,
+                @NonNull String[] headerKeyValuePairs) {
+        AllocateSpaceResult result = new AllocateSpaceResult();
+        try {
+            result.mFreeSpaceRequired = mUpdateEngine.allocateSpaceForPayload(
+                    payloadMetadataFilename,
+                    headerKeyValuePairs);
+            result.mErrorCode = result.mFreeSpaceRequired == 0
+                    ? ErrorCodeConstants.SUCCESS
+                    : ErrorCodeConstants.NOT_ENOUGH_SPACE;
+            return result;
+        } catch (ServiceSpecificException e) {
+            result.mErrorCode = e.errorCode;
+            result.mFreeSpaceRequired = 0;
+            return result;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Cleanup files used by the previous update and free up space after the
+     * device has been booted successfully into the new build.
+     *
+     * <p>In particular, this function waits until delta files for snapshots for
+     * Virtual A/B update are merged to OS partitions, then delete these delta
+     * files.
+     *
+     * <p>This function is synchronous and may take a non-trivial amount of
+     * time. Callers should call this function in a background thread.
+     *
+     * <p>This function does not delete payload binaries downloaded for a
+     * non-streaming OTA update.
+     *
+     * @return One of the following:
+     * <ul>
+     * <li>{@link ErrorCodeConstants#SUCCESS} if execution is successful.</li>
+     * <li>{@link ErrorCodeConstants#ERROR} if a transient error has occurred.
+     * The device should be able to recover after a reboot. The function should
+     * be retried after the reboot.</li>
+     * <li>{@link ErrorCodeConstants#DEVICE_CORRUPTED} if a permanent error is
+     * encountered. Device is corrupted, and future updates must not be applied.
+     * The device cannot recover without flashing and factory resets.
+     * </ul>
+     *
+     * @throws ServiceSpecificException if other transient errors has occurred.
+     * A reboot may or may not help resolving the issue.
+     */
+    @ErrorCode
+    public int cleanupAppliedPayload() {
+        try {
+            return mUpdateEngine.cleanupSuccessfulUpdate();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/os/UpdateEngineCallback.java b/core/java/android/os/UpdateEngineCallback.java
index f07294e..7fe7024 100644
--- a/core/java/android/os/UpdateEngineCallback.java
+++ b/core/java/android/os/UpdateEngineCallback.java
@@ -44,5 +44,6 @@
      * unsuccessfully. The value of {@code errorCode} will be one of the
      * values from {@link UpdateEngine.ErrorCodeConstants}.
      */
-    public abstract void onPayloadApplicationComplete(int errorCode);
+    public abstract void onPayloadApplicationComplete(
+            @UpdateEngine.ErrorCode int errorCode);
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 6e199ce3..d8fadfb 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1850,10 +1850,7 @@
      * Checks if the calling app is running in a managed profile.
      *
      * @return whether the caller is in a managed profile.
-     * @hide
      */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public boolean isManagedProfile() {
         // No need for synchronization.  Once it becomes non-null, it'll be non-null forever.
         // Worst case we might end up calling the AIDL method multiple times but that's fine.
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 62b8953..3846f89 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -594,6 +594,8 @@
             argsForZygote.add("--mount-external-legacy");
         } else if (mountExternal == Zygote.MOUNT_EXTERNAL_PASS_THROUGH) {
             argsForZygote.add("--mount-external-pass-through");
+        } else if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) {
+            argsForZygote.add("--mount-external-android-writable");
         }
 
         argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 8959fcf..f0a1174 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -31,6 +31,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.annotation.BytesLong;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -72,6 +73,7 @@
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.sysprop.VoldProperties;
@@ -93,7 +95,6 @@
 import com.android.internal.os.FuseAppLoop;
 import com.android.internal.os.FuseUnavailableMountException;
 import com.android.internal.os.RoSystemProperties;
-import com.android.internal.os.SomeArgs;
 import com.android.internal.util.Preconditions;
 
 import dalvik.system.BlockGuard;
@@ -114,6 +115,7 @@
 import java.util.Objects;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -305,109 +307,85 @@
     private final Looper mLooper;
     private final AtomicInteger mNextNonce = new AtomicInteger(0);
 
+    @GuardedBy("mDelegates")
     private final ArrayList<StorageEventListenerDelegate> mDelegates = new ArrayList<>();
 
-    private static class StorageEventListenerDelegate extends IStorageEventListener.Stub implements
-            Handler.Callback {
-        private static final int MSG_STORAGE_STATE_CHANGED = 1;
-        private static final int MSG_VOLUME_STATE_CHANGED = 2;
-        private static final int MSG_VOLUME_RECORD_CHANGED = 3;
-        private static final int MSG_VOLUME_FORGOTTEN = 4;
-        private static final int MSG_DISK_SCANNED = 5;
-        private static final int MSG_DISK_DESTROYED = 6;
+    private class StorageEventListenerDelegate extends IStorageEventListener.Stub {
+        final Executor mExecutor;
+        final StorageEventListener mListener;
+        final StorageVolumeCallback mCallback;
 
-        final StorageEventListener mCallback;
-        final Handler mHandler;
-
-        public StorageEventListenerDelegate(StorageEventListener callback, Looper looper) {
+        public StorageEventListenerDelegate(@NonNull Executor executor,
+                @NonNull StorageEventListener listener, @NonNull StorageVolumeCallback callback) {
+            mExecutor = executor;
+            mListener = listener;
             mCallback = callback;
-            mHandler = new Handler(looper, this);
-        }
-
-        @Override
-        public boolean handleMessage(Message msg) {
-            final SomeArgs args = (SomeArgs) msg.obj;
-            switch (msg.what) {
-                case MSG_STORAGE_STATE_CHANGED:
-                    mCallback.onStorageStateChanged((String) args.arg1, (String) args.arg2,
-                            (String) args.arg3);
-                    args.recycle();
-                    return true;
-                case MSG_VOLUME_STATE_CHANGED:
-                    mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3);
-                    args.recycle();
-                    return true;
-                case MSG_VOLUME_RECORD_CHANGED:
-                    mCallback.onVolumeRecordChanged((VolumeRecord) args.arg1);
-                    args.recycle();
-                    return true;
-                case MSG_VOLUME_FORGOTTEN:
-                    mCallback.onVolumeForgotten((String) args.arg1);
-                    args.recycle();
-                    return true;
-                case MSG_DISK_SCANNED:
-                    mCallback.onDiskScanned((DiskInfo) args.arg1, args.argi2);
-                    args.recycle();
-                    return true;
-                case MSG_DISK_DESTROYED:
-                    mCallback.onDiskDestroyed((DiskInfo) args.arg1);
-                    args.recycle();
-                    return true;
-            }
-            args.recycle();
-            return false;
         }
 
         @Override
         public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException {
-            // Ignored
+            mExecutor.execute(() -> {
+                mListener.onUsbMassStorageConnectionChanged(connected);
+            });
         }
 
         @Override
         public void onStorageStateChanged(String path, String oldState, String newState) {
-            final SomeArgs args = SomeArgs.obtain();
-            args.arg1 = path;
-            args.arg2 = oldState;
-            args.arg3 = newState;
-            mHandler.obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget();
+            mExecutor.execute(() -> {
+                mListener.onStorageStateChanged(path, oldState, newState);
+
+                if (path != null) {
+                    for (StorageVolume sv : getStorageVolumes()) {
+                        if (Objects.equals(path, sv.getPath())) {
+                            mCallback.onStateChanged(sv);
+                        }
+                    }
+                }
+            });
         }
 
         @Override
         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
-            final SomeArgs args = SomeArgs.obtain();
-            args.arg1 = vol;
-            args.argi2 = oldState;
-            args.argi3 = newState;
-            mHandler.obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget();
+            mExecutor.execute(() -> {
+                mListener.onVolumeStateChanged(vol, oldState, newState);
+
+                final File path = vol.getPathForUser(UserHandle.myUserId());
+                if (path != null) {
+                    for (StorageVolume sv : getStorageVolumes()) {
+                        if (Objects.equals(path.getAbsolutePath(), sv.getPath())) {
+                            mCallback.onStateChanged(sv);
+                        }
+                    }
+                }
+            });
         }
 
         @Override
         public void onVolumeRecordChanged(VolumeRecord rec) {
-            final SomeArgs args = SomeArgs.obtain();
-            args.arg1 = rec;
-            mHandler.obtainMessage(MSG_VOLUME_RECORD_CHANGED, args).sendToTarget();
+            mExecutor.execute(() -> {
+                mListener.onVolumeRecordChanged(rec);
+            });
         }
 
         @Override
         public void onVolumeForgotten(String fsUuid) {
-            final SomeArgs args = SomeArgs.obtain();
-            args.arg1 = fsUuid;
-            mHandler.obtainMessage(MSG_VOLUME_FORGOTTEN, args).sendToTarget();
+            mExecutor.execute(() -> {
+                mListener.onVolumeForgotten(fsUuid);
+            });
         }
 
         @Override
         public void onDiskScanned(DiskInfo disk, int volumeCount) {
-            final SomeArgs args = SomeArgs.obtain();
-            args.arg1 = disk;
-            args.argi2 = volumeCount;
-            mHandler.obtainMessage(MSG_DISK_SCANNED, args).sendToTarget();
+            mExecutor.execute(() -> {
+                mListener.onDiskScanned(disk, volumeCount);
+            });
         }
 
         @Override
         public void onDiskDestroyed(DiskInfo disk) throws RemoteException {
-            final SomeArgs args = SomeArgs.obtain();
-            args.arg1 = disk;
-            mHandler.obtainMessage(MSG_DISK_DESTROYED, args).sendToTarget();
+            mExecutor.execute(() -> {
+                mListener.onDiskDestroyed(disk);
+            });
         }
     }
 
@@ -525,8 +503,8 @@
     @UnsupportedAppUsage
     public void registerListener(StorageEventListener listener) {
         synchronized (mDelegates) {
-            final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(listener,
-                    mLooper);
+            final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(
+                    mContext.getMainExecutor(), listener, new StorageVolumeCallback());
             try {
                 mStorageManager.registerListener(delegate);
             } catch (RemoteException e) {
@@ -548,7 +526,76 @@
         synchronized (mDelegates) {
             for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) {
                 final StorageEventListenerDelegate delegate = i.next();
-                if (delegate.mCallback == listener) {
+                if (delegate.mListener == listener) {
+                    try {
+                        mStorageManager.unregisterListener(delegate);
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                    i.remove();
+                }
+            }
+        }
+    }
+
+    /**
+     * Callback that delivers {@link StorageVolume} related events.
+     * <p>
+     * For example, this can be used to detect when a volume changes to the
+     * {@link Environment#MEDIA_MOUNTED} or {@link Environment#MEDIA_UNMOUNTED}
+     * states.
+     *
+     * @see StorageManager#registerStorageVolumeCallback
+     * @see StorageManager#unregisterStorageVolumeCallback
+     */
+    public static class StorageVolumeCallback {
+        /**
+         * Called when {@link StorageVolume#getState()} changes, such as
+         * changing to the {@link Environment#MEDIA_MOUNTED} or
+         * {@link Environment#MEDIA_UNMOUNTED} states.
+         * <p>
+         * The given argument is a snapshot in time and can be used to process
+         * events in the order they occurred, or you can call
+         * {@link StorageManager#getStorageVolumes()} to observe the latest
+         * value.
+         */
+        public void onStateChanged(@NonNull StorageVolume volume) { }
+    }
+
+    /**
+     * Registers the given callback to listen for {@link StorageVolume} changes.
+     * <p>
+     * For example, this can be used to detect when a volume changes to the
+     * {@link Environment#MEDIA_MOUNTED} or {@link Environment#MEDIA_UNMOUNTED}
+     * states.
+     *
+     * @see StorageManager#unregisterStorageVolumeCallback
+     */
+    public void registerStorageVolumeCallback(@CallbackExecutor @NonNull Executor executor,
+            @NonNull StorageVolumeCallback callback) {
+        synchronized (mDelegates) {
+            final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(
+                    executor, new StorageEventListener(), callback);
+            try {
+                mStorageManager.registerListener(delegate);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            mDelegates.add(delegate);
+        }
+    }
+
+    /**
+     * Unregisters the given callback from listening for {@link StorageVolume}
+     * changes.
+     *
+     * @see StorageManager#registerStorageVolumeCallback
+     */
+    public void unregisterStorageVolumeCallback(@NonNull StorageVolumeCallback callback) {
+        synchronized (mDelegates) {
+            for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) {
+                final StorageEventListenerDelegate delegate = i.next();
+                if (delegate.mCallback == callback) {
                     try {
                         mStorageManager.unregisterListener(delegate);
                     } catch (RemoteException e) {
@@ -829,7 +876,14 @@
      */
     public @NonNull UUID getUuidForPath(@NonNull File path) throws IOException {
         Preconditions.checkNotNull(path);
-        final String pathString = path.getCanonicalPath();
+        String pathString = path.getCanonicalPath();
+        if (path.getPath().startsWith("/sdcard")) {
+            // On FUSE enabled devices, realpath(2) /sdcard is /mnt/user/<userid>/emulated/<userid>
+            // as opposed to /storage/emulated/<userid>.
+            // And vol.path below expects to match with a path starting with /storage
+            pathString = pathString.replaceFirst("^/mnt/user/[0-9]+/", "/storage/");
+        }
+
         if (FileUtils.contains(Environment.getDataDirectory().getAbsolutePath(), pathString)) {
             return UUID_DEFAULT;
         }
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 2ab226f..e251f80 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -162,9 +163,13 @@
         mState = in.readString();
     }
 
-    /** {@hide} */
-    @UnsupportedAppUsage
-    public String getId() {
+    /**
+     * Return an opaque ID that can be used to identify this volume.
+     *
+     * @hide
+     */
+    @SystemApi
+    public @NonNull String getId() {
         return mId;
     }
 
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 0a3c333..ef8a286 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -553,7 +553,9 @@
         /**
          * Flag indicating that a document is a directory that wants to block itself
          * from being selected when the user launches an {@link Intent#ACTION_OPEN_DOCUMENT_TREE}
-         * intent. Only valid when {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
+         * intent. Individual files can still be selected when launched via other intents
+         * like {@link Intent#ACTION_OPEN_DOCUMENT} and {@link Intent#ACTION_GET_CONTENT}.
+         * Only valid when {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
          * <p>
          * Note that this flag <em>only</em> applies to the single directory to which it is
          * applied. It does <em>not</em> block the user from selecting either a parent or
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bbaf94a..a31c3d1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -68,7 +68,6 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.speech.tts.TextToSpeech;
-import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 import android.util.AndroidException;
 import android.util.ArrayMap;
@@ -219,7 +218,9 @@
      * @hide
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_TETHER_PROVISIONING =
+    @SystemApi
+    @TestApi
+    public static final String ACTION_TETHER_PROVISIONING_UI =
             "android.settings.TETHER_PROVISIONING_UI";
 
     /**
@@ -388,6 +389,21 @@
             "android.settings.MANAGE_UNKNOWN_APP_SOURCES";
 
     /**
+     * Activity Action: Show settings to allow configuration of cross-profile access for apps
+     *
+     * Input: Optionally, the Intent's data URI can specify the application package name to
+     * directly invoke the management GUI specific to the package name. For example
+     * "package:com.my.app".
+     * <p>
+     * Output: Nothing.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_CROSS_PROFILE_ACCESS =
+            "android.settings.MANAGE_CROSS_PROFILE_ACCESS";
+
+    /**
      * Activity Action: Show the "Open by Default" page in a particular application's details page.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
@@ -9698,6 +9714,8 @@
          * is interpreted as |false|.
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled";
 
         /**
@@ -12459,16 +12477,17 @@
 
         /**
          * Whether the Volte is enabled. If this setting is not set then we use the Carrier Config
-         * value {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
+         * value
+         * {@link android.telephony.CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
          * <p>
          * Type: int (0 for false, 1 for true)
          * @hide
-         * @deprecated Use {@link android.telephony.SubscriptionManager#ENHANCED_4G_MODE_ENABLED}
+         * @deprecated Use {@link android.provider.Telephony.SimInfo#ENHANCED_4G_MODE_ENABLED}
          * instead.
          */
         @Deprecated
         public static final String ENHANCED_4G_MODE_ENABLED =
-                SubscriptionManager.ENHANCED_4G_MODE_ENABLED;
+                Telephony.SimInfo.ENHANCED_4G_MODE_ENABLED;
 
         /**
          * Whether VT (Video Telephony over IMS) is enabled
@@ -12476,10 +12495,10 @@
          * Type: int (0 for false, 1 for true)
          *
          * @hide
-         * @deprecated Use {@link android.telephony.SubscriptionManager#VT_IMS_ENABLED} instead.
+         * @deprecated Use {@link android.provider.Telephony.SimInfo#VT_IMS_ENABLED} instead.
          */
         @Deprecated
-        public static final String VT_IMS_ENABLED = SubscriptionManager.VT_IMS_ENABLED;
+        public static final String VT_IMS_ENABLED = Telephony.SimInfo.VT_IMS_ENABLED;
 
         /**
          * Whether WFC is enabled
@@ -12487,10 +12506,10 @@
          * Type: int (0 for false, 1 for true)
          *
          * @hide
-         * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ENABLED} instead.
+         * @deprecated Use {@link android.provider.Telephony.SimInfo#WFC_IMS_ENABLED} instead.
          */
         @Deprecated
-        public static final String WFC_IMS_ENABLED = SubscriptionManager.WFC_IMS_ENABLED;
+        public static final String WFC_IMS_ENABLED = Telephony.SimInfo.WFC_IMS_ENABLED;
 
         /**
          * WFC mode on home/non-roaming network.
@@ -12498,10 +12517,10 @@
          * Type: int - 2=Wi-Fi preferred, 1=Cellular preferred, 0=Wi-Fi only
          *
          * @hide
-         * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_MODE} instead.
+         * @deprecated Use {@link android.provider.Telephony.SimInfo#WFC_IMS_MODE} instead.
          */
         @Deprecated
-        public static final String WFC_IMS_MODE = SubscriptionManager.WFC_IMS_MODE;
+        public static final String WFC_IMS_MODE = Telephony.SimInfo.WFC_IMS_MODE;
 
         /**
          * WFC mode on roaming network.
@@ -12509,11 +12528,11 @@
          * Type: int - see {@link #WFC_IMS_MODE} for values
          *
          * @hide
-         * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ROAMING_MODE}
+         * @deprecated Use {@link android.provider.Telephony.SimInfo#WFC_IMS_ROAMING_MODE}
          * instead.
          */
         @Deprecated
-        public static final String WFC_IMS_ROAMING_MODE = SubscriptionManager.WFC_IMS_ROAMING_MODE;
+        public static final String WFC_IMS_ROAMING_MODE = Telephony.SimInfo.WFC_IMS_ROAMING_MODE;
 
         /**
          * Whether WFC roaming is enabled
@@ -12521,12 +12540,12 @@
          * Type: int (0 for false, 1 for true)
          *
          * @hide
-         * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ROAMING_ENABLED}
+         * @deprecated Use {@link android.provider.Telephony.SimInfo#WFC_IMS_ROAMING_ENABLED}
          * instead
          */
         @Deprecated
         public static final String WFC_IMS_ROAMING_ENABLED =
-                SubscriptionManager.WFC_IMS_ROAMING_ENABLED;
+                Telephony.SimInfo.WFC_IMS_ROAMING_ENABLED;
 
         /**
          * Whether user can enable/disable LTE as a preferred network. A carrier might control
@@ -14400,6 +14419,42 @@
     };
 
     /**
+     * Activity Action: Show screen for controlling which apps have access to manage external
+     * storage.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+     * <p>
+     * If you want to control a specific app's access to manage external storage, use
+     * {@link #ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION} instead.
+     * <p>
+     * Output: Nothing.
+     * @see #ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION =
+            "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION";
+
+    /**
+     * Activity Action: Show screen for controlling if the app specified in the data URI of the
+     * intent can manage external storage.
+     * <p>
+     * Launching the corresponding activity requires the permission
+     * {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE}.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+     * <p>
+     * Input: The Intent's data URI MUST specify the application package name whose ability of
+     * managing external storage you want to control.
+     * For example "package:com.my.app".
+     * <p>
+     * Output: Nothing.
+     * @see #ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION =
+            "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
+
+    /**
      * Performs a strict and comprehensive check of whether a calling package is allowed to
      * write/modify system settings, as the condition differs for pre-M, M+, and
      * privileged/preinstalled apps. If the provided uid does not match the
@@ -14425,8 +14480,9 @@
      * current time.
      * @hide
      */
-    public static boolean checkAndNoteWriteSettingsOperation(Context context, int uid,
-            String callingPackage, boolean throwException) {
+    @SystemApi
+    public static boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid,
+            @NonNull String callingPackage, boolean throwException) {
         return isCallingPackageAllowedToPerformAppOpsProtectedOperation(context, uid,
                 callingPackage, throwException, AppOpsManager.OP_WRITE_SETTINGS,
                 PM_WRITE_SETTINGS, true);
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 1d9bdb8..f369064 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -38,11 +38,13 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Parcel;
+import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SmsMessage;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.UiccAccessRule;
 import android.text.TextUtils;
 import android.util.Patterns;
 
@@ -4140,7 +4142,6 @@
          *     <li>{@link #ENABLE_CMAS_PRESIDENTIAL_PREF}</li>
          *     <li>{@link #ENABLE_ALERT_VIBRATION_PREF}</li>
          *     <li>{@link #ENABLE_EMERGENCY_PERF}</li>
-         *     <li>{@link #ENABLE_FULL_VOLUME_PREF}</li>
          *     <li>{@link #ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF}</li>
          * </ul>
          * @hide
@@ -4203,10 +4204,6 @@
             public static final @NonNull String ENABLE_EMERGENCY_PERF =
                     "enable_emergency_alerts";
 
-            /** Preference to enable volume for alerts */
-            public static final @NonNull String ENABLE_FULL_VOLUME_PREF =
-                    "use_full_volume";
-
             /** Preference to enable receive alerts in second language */
             public static final @NonNull String ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF =
                     "receive_cmas_in_second_language";
@@ -4980,5 +4977,402 @@
          */
         @NonNull
         public static final Uri CONTENT_URI = Uri.parse("content://telephony/siminfo");
+
+        /**
+         * TelephonyProvider unique key column name is the subscription id.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id";
+
+        /**
+         * TelephonyProvider column name for a unique identifier for the subscription within the
+         * specific subscription type. For example, it contains SIM ICC Identifier subscriptions
+         * on Local SIMs. and Mac-address for Remote-SIM Subscriptions for Bluetooth devices.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String ICC_ID = "icc_id";
+
+        /**
+         * TelephonyProvider column name for user SIM_SlOT_INDEX
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String SIM_SLOT_INDEX = "sim_id";
+
+        /**
+         * SIM is not inserted
+         */
+        public static final int SIM_NOT_INSERTED = -1;
+
+        /**
+         * TelephonyProvider column name Subscription-type.
+         * <P>Type: INTEGER (int)</P> {@link #SUBSCRIPTION_TYPE_LOCAL_SIM} for Local-SIM
+         * Subscriptions, {@link #SUBSCRIPTION_TYPE_REMOTE_SIM} for Remote-SIM Subscriptions.
+         * Default value is 0.
+         */
+        public static final String SUBSCRIPTION_TYPE = "subscription_type";
+
+        /**
+         * This constant is to designate a subscription as a Local-SIM Subscription.
+         * <p> A Local-SIM can be a physical SIM inserted into a sim-slot in the device, or eSIM on
+         * the device.
+         * </p>
+         */
+        public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0;
+
+        /**
+         * This constant is to designate a subscription as a Remote-SIM Subscription.
+         * <p>
+         * A Remote-SIM subscription is for a SIM on a phone connected to this device via some
+         * connectivity mechanism, for example bluetooth. Similar to Local SIM, this subscription
+         * can be used for SMS, Voice and data by proxying data through the connected device.
+         * Certain data of the SIM, such as IMEI, are not accessible for Remote SIMs.
+         * </p>
+         *
+         * <p>
+         * A Remote-SIM is available only as long the phone stays connected to this device.
+         * When the phone disconnects, Remote-SIM subscription is removed from this device and is
+         * no longer known. All data associated with the subscription, such as stored SMS, call
+         * logs, contacts etc, are removed from this device.
+         * </p>
+         *
+         * <p>
+         * If the phone re-connects to this device, a new Remote-SIM subscription is created for
+         * the phone. The Subscription Id associated with the new subscription is different from
+         * the Subscription Id of the previous Remote-SIM subscription created (and removed) for the
+         * phone; i.e., new Remote-SIM subscription treats the reconnected phone as a Remote-SIM
+         * that was never seen before.
+         * </p>
+         */
+        public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1;
+
+        /**
+         * TelephonyProvider column name data_enabled_override_rules.
+         * It's a list of rules for overriding data enabled settings. The syntax is
+         * For example, "mms=nonDefault" indicates enabling data for mms in non-default
+         * subscription.
+         * "default=nonDefault&inVoiceCall" indicates enabling data for internet in non-default
+         * subscription and while is in voice call.
+         *
+         * Default value is empty string.
+         */
+        public static final String DATA_ENABLED_OVERRIDE_RULES = "data_enabled_override_rules";
+
+        /**
+         * TelephonyProvider column name for user displayed name.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String DISPLAY_NAME = "display_name";
+
+        /**
+         * TelephonyProvider column name for the service provider name for the SIM.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String CARRIER_NAME = "carrier_name";
+
+        /**
+         * TelephonyProvider column name for source of the user displayed name.
+         * <P>Type: INT (int)</P> with one of the NAME_SOURCE_XXXX values below
+         */
+        public static final String NAME_SOURCE = "name_source";
+
+        /** The name_source is the default, which is from the carrier id. */
+        public static final int NAME_SOURCE_DEFAULT = 0;
+
+        /**
+         * The name_source is from SIM EF_SPN.
+         */
+        public static final int NAME_SOURCE_SIM_SPN = 1;
+
+        /**
+         * The name_source is from user input
+         */
+        public static final int NAME_SOURCE_USER_INPUT = 2;
+
+        /**
+         * The name_source is carrier (carrier app, carrier config, etc.)
+         */
+        public static final int NAME_SOURCE_CARRIER = 3;
+
+        /**
+         * The name_source is from SIM EF_PNN.
+         */
+        public static final int NAME_SOURCE_SIM_PNN = 4;
+
+        /**
+         * TelephonyProvider column name for the color of a SIM.
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String COLOR = "color";
+
+        /** TelephonyProvider column name for the default color of a SIM {@hide} */
+        public static final int COLOR_DEFAULT = 0;
+
+        /**
+         * TelephonyProvider column name for the phone number of a SIM.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String NUMBER = "number";
+
+        /**
+         * TelephonyProvider column name for the number display format of a SIM.
+         * <P>Type: INTEGER (int)</P>
+         * @hide
+         */
+        public static final String DISPLAY_NUMBER_FORMAT = "display_number_format";
+
+        /**
+         * TelephonyProvider column name for the default display format of a SIM
+         * @hide
+         */
+        public static final int DISPLAY_NUMBER_DEFAULT = 1;
+
+        /**
+         * TelephonyProvider column name for whether data roaming is enabled.
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String DATA_ROAMING = "data_roaming";
+
+        /** Indicates that data roaming is enabled for a subscription */
+        public static final int DATA_ROAMING_ENABLE = 1;
+
+        /** Indicates that data roaming is disabled for a subscription */
+        public static final int DATA_ROAMING_DISABLE = 0;
+
+        /** TelephonyProvider column name for default data roaming setting: disable */
+        public static final int DATA_ROAMING_DEFAULT = DATA_ROAMING_DISABLE;
+
+        /**
+         * TelephonyProvider column name for subscription carrier id.
+         * @see TelephonyManager#getSimCarrierId()
+         * <p>Type: INTEGER (int) </p>
+         */
+        public static final String CARRIER_ID = "carrier_id";
+
+        /**
+         * A comma-separated list of EHPLMNs associated with the subscription
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String EHPLMNS = "ehplmns";
+
+        /**
+         * A comma-separated list of HPLMNs associated with the subscription
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String HPLMNS = "hplmns";
+
+        /**
+         * TelephonyProvider column name for the MCC associated with a SIM, stored as a string.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String MCC_STRING = "mcc_string";
+
+        /**
+         * TelephonyProvider column name for the MNC associated with a SIM, stored as a string.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String MNC_STRING = "mnc_string";
+
+        /**
+         * TelephonyProvider column name for the MCC associated with a SIM.
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String MCC = "mcc";
+
+        /**
+         * TelephonyProvider column name for the MNC associated with a SIM.
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String MNC = "mnc";
+
+        /**
+         * TelephonyProvider column name for the iso country code associated with a SIM.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String ISO_COUNTRY_CODE = "iso_country_code";
+
+        /**
+         * TelephonyProvider column name for the sim provisioning status associated with a SIM.
+         * <P>Type: INTEGER (int)</P>
+         * @hide
+         */
+        public static final String SIM_PROVISIONING_STATUS = "sim_provisioning_status";
+
+        /** The sim is provisioned {@hide} */
+        public static final int SIM_PROVISIONED = 0;
+
+        /**
+         * TelephonyProvider column name for whether a subscription is embedded (that is, present on
+         * an eSIM).
+         * <p>Type: INTEGER (int), 1 for embedded or 0 for non-embedded.
+         */
+        public static final String IS_EMBEDDED = "is_embedded";
+
+        /**
+         * TelephonyProvider column name for SIM card identifier. For UICC card it is the ICCID of
+         * the current enabled profile on the card, while for eUICC card it is the EID of the card.
+         * <P>Type: TEXT (String)</P>
+         */
+        public static final String CARD_ID = "card_id";
+
+        /**
+         * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from
+         * {@link UiccAccessRule#encodeRules}. Only present if {@link #IS_EMBEDDED} is 1.
+         * <p>TYPE: BLOB
+         */
+        public static final String ACCESS_RULES = "access_rules";
+
+        /**
+         * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from
+         * {@link UiccAccessRule#encodeRules} but for the rules that come from CarrierConfigs.
+         * Only present if there are access rules in CarrierConfigs
+         * <p>TYPE: BLOB
+         */
+        public static final String ACCESS_RULES_FROM_CARRIER_CONFIGS =
+                "access_rules_from_carrier_configs";
+
+        /**
+         * TelephonyProvider column name identifying whether an embedded subscription is on a
+         * removable card. Such subscriptions are marked inaccessible as soon as the current card
+         * is removed. Otherwise, they will remain accessible unless explicitly deleted. Only
+         * present if {@link #IS_EMBEDDED} is 1.
+         * <p>TYPE: INTEGER (int), 1 for removable or 0 for non-removable.
+         */
+        public static final String IS_REMOVABLE = "is_removable";
+
+        /** TelephonyProvider column name for extreme threat in CB settings */
+        public static final String CB_EXTREME_THREAT_ALERT = "enable_cmas_extreme_threat_alerts";
+
+        /** TelephonyProvider column name for severe threat in CB settings */
+        public static final String CB_SEVERE_THREAT_ALERT = "enable_cmas_severe_threat_alerts";
+
+        /** TelephonyProvider column name for amber alert in CB settings */
+        public static final String CB_AMBER_ALERT = "enable_cmas_amber_alerts";
+
+        /** TelephonyProvider column name for emergency alert in CB settings */
+        public static final String CB_EMERGENCY_ALERT = "enable_emergency_alerts";
+
+        /** TelephonyProvider column name for alert sound duration in CB settings */
+        public static final String CB_ALERT_SOUND_DURATION = "alert_sound_duration";
+
+        /** TelephonyProvider column name for alert reminder interval in CB settings */
+        public static final String CB_ALERT_REMINDER_INTERVAL = "alert_reminder_interval";
+
+        /** TelephonyProvider column name for enabling vibrate in CB settings */
+        public static final String CB_ALERT_VIBRATE = "enable_alert_vibrate";
+
+        /** TelephonyProvider column name for enabling alert speech in CB settings */
+        public static final String CB_ALERT_SPEECH = "enable_alert_speech";
+
+        /** TelephonyProvider column name for ETWS test alert in CB settings */
+        public static final String CB_ETWS_TEST_ALERT = "enable_etws_test_alerts";
+
+        /** TelephonyProvider column name for enable channel50 alert in CB settings */
+        public static final String CB_CHANNEL_50_ALERT = "enable_channel_50_alerts";
+
+        /** TelephonyProvider column name for CMAS test alert in CB settings */
+        public static final String CB_CMAS_TEST_ALERT = "enable_cmas_test_alerts";
+
+        /** TelephonyProvider column name for Opt out dialog in CB settings */
+        public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog";
+
+        /**
+         * TelephonyProvider column name for enable Volte.
+         *
+         * If this setting is not initialized (set to -1)  then we use the Carrier Config value
+         * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
+         */
+        public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
+
+        /** TelephonyProvider column name for enable VT (Video Telephony over IMS) */
+        public static final String VT_IMS_ENABLED = "vt_ims_enabled";
+
+        /** TelephonyProvider column name for enable Wifi calling */
+        public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
+
+        /** TelephonyProvider column name for Wifi calling mode */
+        public static final String WFC_IMS_MODE = "wfc_ims_mode";
+
+        /** TelephonyProvider column name for Wifi calling mode in roaming */
+        public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode";
+
+        /** TelephonyProvider column name for enable Wifi calling in roaming */
+        public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled";
+
+        /**
+         * TelephonyProvider column name for whether a subscription is opportunistic, that is,
+         * whether the network it connects to is limited in functionality or coverage.
+         * For example, CBRS.
+         * <p>Type: INTEGER (int), 1 for opportunistic or 0 for non-opportunistic.
+         */
+        public static final String IS_OPPORTUNISTIC = "is_opportunistic";
+
+        /**
+         * TelephonyProvider column name for group ID. Subscriptions with same group ID
+         * are considered bundled together, and should behave as a single subscription at
+         * certain scenarios.
+         */
+        public static final String GROUP_UUID = "group_uuid";
+
+        /**
+         * TelephonyProvider column name for group owner. It's the package name who created
+         * the subscription group.
+         */
+        public static final String GROUP_OWNER = "group_owner";
+
+        /**
+         * TelephonyProvider column name for whether a subscription is metered or not, that is,
+         * whether the network it connects to charges for subscription or not. For example, paid
+         * CBRS or unpaid.
+         * @hide
+         */
+        public static final String IS_METERED = "is_metered";
+
+        /**
+         * TelephonyProvider column name for the profile class of a subscription
+         * Only present if {@link #IS_EMBEDDED} is 1.
+         * <P>Type: INTEGER (int)</P>
+         */
+        public static final String PROFILE_CLASS = "profile_class";
+
+        /**
+         * A testing profile can be pre-loaded or downloaded onto
+         * the eUICC and provides connectivity to test equipment
+         * for the purpose of testing the device and the eUICC. It
+         * is not intended to store any operator credentials.
+         */
+        public static final int PROFILE_CLASS_TESTING = 0;
+
+        /**
+         * A provisioning profile is pre-loaded onto the eUICC and
+         * provides connectivity to a mobile network solely for the
+         * purpose of provisioning profiles.
+         */
+        public static final int PROFILE_CLASS_PROVISIONING = 1;
+
+        /**
+         * An operational profile can be pre-loaded or downloaded
+         * onto the eUICC and provides services provided by the
+         * operator.
+         */
+        public static final int PROFILE_CLASS_OPERATIONAL = 2;
+
+        /**
+         * The profile class is unset. This occurs when profile class
+         * info is not available. The subscription either has no profile
+         * metadata or the profile metadata did not encode profile class.
+         */
+        public static final int PROFILE_CLASS_UNSET = -1;
+
+        /** Default profile class */
+        public static final int PROFILE_CLASS_DEFAULT = PROFILE_CLASS_UNSET;
+
+        /**
+         * IMSI (International Mobile Subscriber Identity).
+         * <P>Type: TEXT </P>
+         */
+        public static final String IMSI = "imsi";
+
+        /** Whether uicc applications is set to be enabled or disabled. By default it's enabled. */
+        public static final String UICC_APPLICATIONS_ENABLED = "uicc_applications_enabled";
     }
 }
diff --git a/core/java/android/se/omapi/SEService.java b/core/java/android/se/omapi/SEService.java
index d646e23..00060ab 100644
--- a/core/java/android/se/omapi/SEService.java
+++ b/core/java/android/se/omapi/SEService.java
@@ -22,14 +22,11 @@
 
 package android.se.omapi;
 
-import android.app.ActivityThread;
 import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -143,10 +140,6 @@
             throw new NullPointerException("Arguments must not be null");
         }
 
-        if (!hasOMAPIReaders()) {
-            throw new UnsupportedOperationException("Device does not support any OMAPI reader");
-        }
-
         mContext = context;
         mSEListener.mListener = listener;
         mSEListener.mExecutor = executor;
@@ -277,23 +270,4 @@
             throw new IllegalStateException(e.getMessage());
         }
     }
-
-    /**
-     * Helper to check if this device support any OMAPI readers
-     */
-    private static boolean hasOMAPIReaders() {
-        IPackageManager pm = ActivityThread.getPackageManager();
-        if (pm == null) {
-            Log.e(TAG, "Cannot get package manager, assuming OMAPI readers supported");
-            return true;
-        }
-        try {
-            return pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_UICC, 0)
-                || pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_ESE, 0)
-                || pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_SD, 0);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Package manager query failed, assuming OMAPI readers supported", e);
-            return true;
-        }
-    }
 }
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index d51e4ca..939ae87 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -609,10 +609,12 @@
                         "must add at least 1 dataset when using header or footer");
             }
 
-            for (final Dataset dataset : mDatasets) {
-                if (dataset.getFieldInlinePresentation(0) != null) {
-                    mSupportsInlineSuggestions = true;
-                    break;
+            if (mDatasets != null) {
+                for (final Dataset dataset : mDatasets) {
+                    if (dataset.getFieldInlinePresentation(0) != null) {
+                        mSupportsInlineSuggestions = true;
+                        break;
+                    }
                 }
             }
 
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index 5977baf..4ead3fc 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -49,6 +49,9 @@
     void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel);
     void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId);
     void onNotificationsSeen(in List<String> keys);
+    void onPanelRevealed(int items);
+    void onPanelHidden();
+    void onNotificationVisibilityChanged(String key, boolean isVisible);
     void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded);
     void onNotificationDirectReply(String key);
     void onSuggestedReplySent(String key, in CharSequence reply, int source);
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index da40254..e976e18 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -182,6 +182,32 @@
     }
 
     /**
+     * Implement this to know when the notification panel is revealed
+     *
+     * @param items Number of items on the panel at time of opening
+     */
+    public void onPanelRevealed(int items) {
+
+    }
+
+    /**
+     * Implement this to know when the notification panel is hidden
+     */
+    public void onPanelHidden() {
+
+    }
+
+    /**
+     * Implement this to know when a notification becomes visible or hidden from the user.
+     *
+     * @param key the notification key
+     * @param isVisible whether the notification is visible.
+     */
+    public void onNotificationVisibilityChanged(@NonNull String key, boolean isVisible) {
+
+    }
+
+    /**
      * Implement this to know when a notification change (expanded / collapsed) is visible to user.
      *
      * @param key the notification key
@@ -337,6 +363,30 @@
         }
 
         @Override
+        public void onPanelRevealed(int items) {
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = items;
+            mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_REVEALED,
+                    args).sendToTarget();
+        }
+
+        @Override
+        public void onPanelHidden() {
+            SomeArgs args = SomeArgs.obtain();
+            mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_HIDDEN,
+                    args).sendToTarget();
+        }
+
+        @Override
+        public void onNotificationVisibilityChanged(String key, boolean isVisible) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = key;
+            args.argi1 = isVisible ? 1 : 0;
+            mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_VISIBILITY_CHANGED,
+                    args).sendToTarget();
+        }
+
+        @Override
         public void onNotificationExpansionChanged(String key, boolean isUserAction,
                 boolean isExpanded) {
             SomeArgs args = SomeArgs.obtain();
@@ -394,6 +444,9 @@
         public static final int MSG_ON_SUGGESTED_REPLY_SENT = 6;
         public static final int MSG_ON_ACTION_INVOKED = 7;
         public static final int MSG_ON_ALLOWED_ADJUSTMENTS_CHANGED = 8;
+        public static final int MSG_ON_PANEL_REVEALED = 9;
+        public static final int MSG_ON_PANEL_HIDDEN = 10;
+        public static final int MSG_ON_NOTIFICATION_VISIBILITY_CHANGED = 11;
 
         public MyHandler(Looper looper) {
             super(looper, null, false);
@@ -480,6 +533,25 @@
                     onAllowedAdjustmentsChanged();
                     break;
                 }
+                case MSG_ON_PANEL_REVEALED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    int items = args.argi1;
+                    args.recycle();
+                    onPanelRevealed(items);
+                    break;
+                }
+                case MSG_ON_PANEL_HIDDEN: {
+                    onPanelHidden();
+                    break;
+                }
+                case MSG_ON_NOTIFICATION_VISIBILITY_CHANGED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    String key = (String) args.arg1;
+                    boolean isVisible = args.argi1 == 1;
+                    args.recycle();
+                    onNotificationVisibilityChanged(key, isVisible);
+                    break;
+                }
             }
         }
     }
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 80d054b..fd04f49 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1383,6 +1383,22 @@
         }
 
         @Override
+        public void onPanelRevealed(int items) throws RemoteException {
+            // no-op in the listener
+        }
+
+        @Override
+        public void onPanelHidden() throws RemoteException {
+            // no-op in the listener
+        }
+
+        @Override
+        public void onNotificationVisibilityChanged(
+                String key, boolean isVisible) {
+            // no-op in the listener
+        }
+
+        @Override
         public void onNotificationSnoozedUntilContext(
                 IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId)
                 throws RemoteException {
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 67925bf..0f33998 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -119,7 +119,9 @@
     @IntDef(flag = true, prefix = { "RECOGNITION_FLAG_" }, value = {
             RECOGNITION_FLAG_NONE,
             RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO,
-            RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
+            RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS,
+            RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION,
+            RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION,
     })
     public @interface RecognitionFlags {}
 
@@ -144,6 +146,26 @@
      */
     public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2;
 
+    /**
+     * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+     * if the underlying recognition should use AEC.
+     * This capability may or may not be supported by the system, and support can be queried
+     * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for
+     * this flag is {@link #AUDIO_CAPABILITY_ECHO_CANCELLATION}. If this flag is passed without the
+     * audio capability supported, there will be no audio effect applied.
+     */
+    public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 0x4;
+
+    /**
+     * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+     * if the underlying recognition should use noise suppression.
+     * This capability may or may not be supported by the system, and support can be queried
+     * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for
+     * this flag is {@link #AUDIO_CAPABILITY_NOISE_SUPPRESSION}. If this flag is passed without the
+     * audio capability supported, there will be no audio effect applied.
+     */
+    public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8;
+
     //---- Recognition mode flags. Return codes for getSupportedRecognitionModes() ----//
     // Must be kept in sync with the related attribute defined as searchKeyphraseRecognitionFlags.
 
@@ -168,6 +190,46 @@
     public static final int RECOGNITION_MODE_USER_IDENTIFICATION
             = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
 
+    //-- Audio capabilities. Values in returned bit field for getSupportedAudioCapabilities() --//
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = {
+            AUDIO_CAPABILITY_ECHO_CANCELLATION,
+            AUDIO_CAPABILITY_NOISE_SUPPRESSION,
+    })
+    public @interface AudioCapabilities {}
+
+    /**
+     * If set the underlying module supports AEC.
+     * Returned by {@link #getSupportedAudioCapabilities()}
+     */
+    public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION =
+            SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+
+    /**
+     * If set, the underlying module supports noise suppression.
+     * Returned by {@link #getSupportedAudioCapabilities()}
+     */
+    public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION =
+            SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "MODEL_PARAM_" }, value = {
+            MODEL_PARAM_THRESHOLD_FACTOR,
+    })
+    public @interface ModelParams {}
+
+    /**
+     * Controls the sensitivity threshold adjustment factor for a given model.
+     * Negative value corresponds to less sensitive model (high threshold) and
+     * a positive value corresponds to a more sensitive model (low threshold).
+     * Default value is 0.
+     */
+    public static final int MODEL_PARAM_THRESHOLD_FACTOR =
+            android.hardware.soundtrigger.ModelParams.THRESHOLD_FACTOR;
+
     static final String TAG = "AlwaysOnHotwordDetector";
     static final boolean DBG = false;
 
@@ -198,6 +260,53 @@
     private int mAvailability = STATE_NOT_READY;
 
     /**
+     *  A ModelParamRange is a representation of supported parameter range for a
+     *  given loaded model.
+     */
+    public static final class ModelParamRange {
+        private final SoundTrigger.ModelParamRange mModelParamRange;
+
+        /** @hide */
+        ModelParamRange(SoundTrigger.ModelParamRange modelParamRange) {
+            mModelParamRange = modelParamRange;
+        }
+
+        /**
+         * The inclusive start of supported range.
+         *
+         * @return start of range
+         */
+        public int start() {
+            return mModelParamRange.start;
+        }
+
+        /**
+         * The inclusive end of supported range.
+         *
+         * @return end of range
+         */
+        public int end() {
+            return mModelParamRange.end;
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return mModelParamRange.toString();
+        }
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            return mModelParamRange.equals(obj);
+        }
+
+        @Override
+        public int hashCode() {
+            return mModelParamRange.hashCode();
+        }
+    }
+
+    /**
      * Additional payload for {@link Callback#onDetected}.
      */
     public static class EventPayload {
@@ -385,6 +494,37 @@
     }
 
     /**
+     * Get the audio capabilities supported by the platform which can be enabled when
+     * starting a recognition.
+     *
+     * @see #AUDIO_CAPABILITY_ECHO_CANCELLATION
+     * @see #AUDIO_CAPABILITY_NOISE_SUPPRESSION
+     *
+     * @return Bit field encoding of the AudioCapabilities supported.
+     */
+    @AudioCapabilities
+    public int getSupportedAudioCapabilities() {
+        if (DBG) Slog.d(TAG, "getSupportedAudioCapabilities()");
+        synchronized (mLock) {
+            return getSupportedAudioCapabilitiesLocked();
+        }
+    }
+
+    private int getSupportedAudioCapabilitiesLocked() {
+        try {
+            ModuleProperties properties =
+                    mModelManagementService.getDspModuleProperties(mVoiceInteractionService);
+            if (properties != null) {
+                return properties.audioCapabilities;
+            }
+
+            return 0;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Starts recognition for the associated keyphrase.
      *
      * @see #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
@@ -445,6 +585,83 @@
     }
 
     /**
+     * Set a model specific {@link ModelParams} with the given value. This
+     * parameter will keep its value for the duration the model is loaded regardless of starting and
+     * stopping recognition. Once the model is unloaded, the value will be lost.
+     * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before calling this
+     * method.
+     *
+     * @param modelParam   {@link ModelParams}
+     * @param value        Value to set
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+     *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+     *           if API is not supported by HAL
+     */
+    public int setParameter(@ModelParams int modelParam, int value) {
+        if (DBG) {
+            Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")");
+        }
+
+        synchronized (mLock) {
+            if (mAvailability == STATE_INVALID) {
+                throw new IllegalStateException("setParameter called on an invalid detector");
+            }
+
+            return setParameterLocked(modelParam, value);
+        }
+    }
+
+    /**
+     * Get a model specific {@link ModelParams}. This parameter will keep its value
+     * for the duration the model is loaded regardless of starting and stopping recognition.
+     * Once the model is unloaded, the value will be lost. If the value is not set, a default
+     * value is returned. See {@link ModelParams} for parameter default values.
+     * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before
+     * calling this method.
+     *
+     * @param modelParam   {@link ModelParams}
+     * @return value of parameter
+     */
+    public int getParameter(@ModelParams int modelParam) {
+        if (DBG) {
+            Slog.d(TAG, "getParameter(" + modelParam + ")");
+        }
+
+        synchronized (mLock) {
+            if (mAvailability == STATE_INVALID) {
+                throw new IllegalStateException("getParameter called on an invalid detector");
+            }
+
+            return getParameterLocked(modelParam);
+        }
+    }
+
+    /**
+     * Determine if parameter control is supported for the given model handle.
+     * This method should be checked prior to calling {@link AlwaysOnHotwordDetector#setParameter}
+     * or {@link AlwaysOnHotwordDetector#getParameter}.
+     *
+     * @param modelParam {@link ModelParams}
+     * @return supported range of parameter, null if not supported
+     */
+    @Nullable
+    public ModelParamRange queryParameter(@ModelParams int modelParam) {
+        if (DBG) {
+            Slog.d(TAG, "queryParameter(" + modelParam + ")");
+        }
+
+        synchronized (mLock) {
+            if (mAvailability == STATE_INVALID) {
+                throw new IllegalStateException("queryParameter called on an invalid detector");
+            }
+
+            return queryParameterLocked(modelParam);
+        }
+    }
+
+    /**
      * Creates an intent to start the enrollment for the associated keyphrase.
      * This intent must be invoked using {@link Context#startForegroundService(Intent)}.
      * Starting re-enrollment is only valid if the keyphrase is un-enrolled,
@@ -571,12 +788,21 @@
                 (recognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;
         boolean allowMultipleTriggers =
                 (recognitionFlags&RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0;
+
+        int audioCapabilities = 0;
+        if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) {
+            audioCapabilities |= AUDIO_CAPABILITY_ECHO_CANCELLATION;
+        }
+        if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) {
+            audioCapabilities |= AUDIO_CAPABILITY_NOISE_SUPPRESSION;
+        }
+
         int code = STATUS_ERROR;
         try {
             code = mModelManagementService.startRecognition(mVoiceInteractionService,
                     mKeyphraseMetadata.id, mLocale.toLanguageTag(), mInternalCallback,
                     new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
-                            recognitionExtra, null /* additional data */));
+                            recognitionExtra, null /* additional data */, audioCapabilities));
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException in startRecognition!", e);
         }
@@ -601,6 +827,47 @@
         return code;
     }
 
+    private int setParameterLocked(@ModelParams int modelParam, int value) {
+        try {
+            int code = mModelManagementService.setParameter(mVoiceInteractionService,
+                    mKeyphraseMetadata.id, modelParam, value);
+
+            if (code != STATUS_OK) {
+                Slog.w(TAG, "setParameter failed with error code " + code);
+            }
+
+            return code;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private int getParameterLocked(@ModelParams int modelParam) {
+        try {
+            return mModelManagementService.getParameter(mVoiceInteractionService,
+                    mKeyphraseMetadata.id, modelParam);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Nullable
+    private ModelParamRange queryParameterLocked(@ModelParams int modelParam) {
+        try {
+            SoundTrigger.ModelParamRange modelParamRange =
+                    mModelManagementService.queryParameter(mVoiceInteractionService,
+                            mKeyphraseMetadata.id, modelParam);
+
+            if (modelParamRange == null) {
+                return null;
+            }
+
+            return new ModelParamRange(modelParamRange);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private void notifyStateChangedLocked() {
         Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED);
         message.arg1 = mAvailability;
diff --git a/core/java/android/telephony/WapPushManagerConnector.java b/core/java/android/telephony/WapPushManagerConnector.java
new file mode 100644
index 0000000..a9df506
--- /dev/null
+++ b/core/java/android/telephony/WapPushManagerConnector.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.telephony.IWapPushManager;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * APIs for platform to connect to the WAP push manager service.
+ *
+ * <p>To start connection, {@link #bindToWapPushManagerService} should be called.
+ *
+ * <p>Upon completion {@link #unbindWapPushManagerService} should be called to unbind the service.
+ *
+ * @hide
+ */
+@SystemApi
+public final class WapPushManagerConnector {
+    private final Context mContext;
+
+    private volatile WapPushManagerConnection mConnection;
+    private volatile IWapPushManager mWapPushManager;
+    private String mWapPushManagerPackage;
+
+    /**
+     * The {@link android.content.Intent} that must be declared as handled by the
+     * WAP push manager service.
+     * @hide
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE =
+            "com.android.internal.telephony.IWapPushManager";
+
+    /** @hide */
+    @IntDef(flag = true, prefix = {"RESULT_"}, value = {
+            RESULT_MESSAGE_HANDLED,
+            RESULT_APP_QUERY_FAILED,
+            RESULT_SIGNATURE_NO_MATCH,
+            RESULT_INVALID_RECEIVER_NAME,
+            RESULT_EXCEPTION_CAUGHT,
+            RESULT_FURTHER_PROCESSING,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ProcessMessageResult{}
+
+    /** {@link #processMessage} return value: Message is handled. */
+    public static final int RESULT_MESSAGE_HANDLED = 0x1;
+    /** {@link #processMessage} return value: Application ID or content type was not found. */
+    public static final int RESULT_APP_QUERY_FAILED = 0x2;
+    /** {@link #processMessage} return value: Receiver application signature check failed. */
+    public static final int RESULT_SIGNATURE_NO_MATCH = 0x4;
+    /** {@link #processMessage} return value: Receiver application was not found. */
+    public static final int RESULT_INVALID_RECEIVER_NAME = 0x8;
+    /** {@link #processMessage} return value: Unknown exception. */
+    public static final int RESULT_EXCEPTION_CAUGHT = 0x10;
+    /** {@link #processMessage} return value: further processing needed. */
+    public static final int RESULT_FURTHER_PROCESSING = 0x8000;
+
+    /** The application package name of the WAP push manager service. */
+    private static final String SERVICE_PACKAGE = "com.android.smspush";
+
+    public WapPushManagerConnector(@NonNull Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Binds to the WAP push manager service. This method should be called exactly once.
+     *
+     * @return {@code true} upon successfully binding to a service, {@code false} otherwise
+     */
+    public boolean bindToWapPushManagerService() {
+        Preconditions.checkState(mConnection == null);
+
+        Intent intent = new Intent(SERVICE_INTERFACE);
+        ComponentName component = intent.resolveSystemService(mContext.getPackageManager(), 0);
+        intent.setComponent(component);
+        mConnection = new WapPushManagerConnection();
+        if (component != null
+                && mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) {
+            mWapPushManagerPackage = component.getPackageName();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the package name of WAP push manager service application connected to,
+     * or {@code null} if not connected.
+     */
+    @Nullable
+    public String getConnectedWapPushManagerServicePackage() {
+        return mWapPushManagerPackage;
+    }
+
+    /**
+     * Processes WAP push message and triggers the {@code intent}.
+     *
+     * @see RESULT_MESSAGE_HANDLED
+     * @see RESULT_APP_QUERY_FAILED
+     * @see RESULT_SIGNATURE_NO_MATCH
+     * @see RESULT_INVALID_RECEIVER_NAME
+     * @see RESULT_EXCEPTION_CAUGHT
+     * @see RESULT_FURTHER_PROCESSING
+     */
+    @ProcessMessageResult
+    public int processMessage(
+            @NonNull String applicationId, @NonNull String contentType, @NonNull Intent intent) {
+        try {
+            return mWapPushManager.processMessage(applicationId, contentType, intent);
+        } catch (NullPointerException | RemoteException e) {
+            return RESULT_EXCEPTION_CAUGHT;
+        }
+    }
+
+    /**
+     * Unbinds the WAP push manager service. This method should be called exactly once.
+     */
+    public void unbindWapPushManagerService() {
+        Preconditions.checkNotNull(mConnection);
+
+        mContext.unbindService(mConnection);
+        mConnection = null;
+    }
+
+    private class WapPushManagerConnection implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            // Because we have bound to an explicit
+            // service that is running in our own process, we can
+            // cast its IBinder to a concrete class and directly access it.
+            mWapPushManager = IWapPushManager.Stub.asInterface(service);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            mWapPushManager = null;
+        }
+
+        @Override
+        public void onNullBinding(ComponentName name) {
+            onServiceDisconnected(name);
+        }
+
+        @Override
+        public void onBindingDied(ComponentName name) {
+            onServiceDisconnected(name);
+        }
+    }
+}
diff --git a/core/java/android/text/SpannableStringInternal.java b/core/java/android/text/SpannableStringInternal.java
index b85ef76..4c9328a 100644
--- a/core/java/android/text/SpannableStringInternal.java
+++ b/core/java/android/text/SpannableStringInternal.java
@@ -40,9 +40,10 @@
 
         if (source instanceof Spanned) {
             if (source instanceof SpannableStringInternal) {
-                copySpans((SpannableStringInternal) source, start, end, ignoreNoCopySpan);
+                copySpansFromInternal(
+                        (SpannableStringInternal) source, start, end, ignoreNoCopySpan);
             } else {
-                copySpans((Spanned) source, start, end, ignoreNoCopySpan);
+                copySpansFromSpanned((Spanned) source, start, end, ignoreNoCopySpan);
             }
         }
     }
@@ -65,7 +66,7 @@
      * @param end End index in the source object.
      * @param ignoreNoCopySpan whether to copy NoCopySpans in the {@code source}
      */
-    private void copySpans(Spanned src, int start, int end, boolean ignoreNoCopySpan) {
+    private void copySpansFromSpanned(Spanned src, int start, int end, boolean ignoreNoCopySpan) {
         Object[] spans = src.getSpans(start, end, Object.class);
 
         for (int i = 0; i < spans.length; i++) {
@@ -94,7 +95,7 @@
      * @param end End index in the source object.
      * @param ignoreNoCopySpan copy NoCopySpan for backward compatible reasons.
      */
-    private void copySpans(SpannableStringInternal src, int start, int end,
+    private void copySpansFromInternal(SpannableStringInternal src, int start, int end,
             boolean ignoreNoCopySpan) {
         int count = 0;
         final int[] srcData = src.mSpanData;
@@ -555,12 +556,12 @@
      */
     @UnsupportedAppUsage
     private void copySpans(Spanned src, int start, int end) {
-        copySpans(src, start, end, false);
+        copySpansFromSpanned(src, start, end, false);
     }
 
     @UnsupportedAppUsage
     private void copySpans(SpannableStringInternal src, int start, int end) {
-        copySpans(src, start, end, false);
+        copySpansFromInternal(src, start, end, false);
     }
 
 
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index 3f679bba..f324113 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -387,6 +387,26 @@
     public static native int println_native(int bufID, int priority, String tag, String msg);
 
     /**
+     * Send a log message to the "radio" log buffer, which can be dumped with
+     * {@code adb logcat -b radio}.
+     *
+     * <p>Only the telephony mainline module should use it.
+     *
+     * <p>Note ART will protect {@code MODULE_LIBRARIES} system APIs from regular app code.
+     *
+     * @param priority Log priority.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param message The message you would like logged.
+     * @hide
+     */
+    // @SystemApi(client= SystemApi.Client.MODULE_LIBRARIES) // TODO Uncomment once http://ag/9956147 is in.
+    public static int logToRadioBuffer(@Level int priority, @Nullable String tag,
+            @Nullable String message) {
+        return println_native(LOG_ID_RADIO, priority, tag, message);
+    }
+
+    /**
      * Return the maximum payload the log daemon accepts without truncation.
      * @return LOGGER_ENTRY_MAX_PAYLOAD.
      */
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 2223d14..fa994ba 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -25,6 +25,7 @@
 import android.net.NetworkInfo;
 import android.net.SntpClient;
 import android.os.SystemClock;
+import android.os.TimestampedValue;
 import android.provider.Settings;
 import android.text.TextUtils;
 
diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl
new file mode 100644
index 0000000..429c3ae
--- /dev/null
+++ b/core/java/android/view/IDisplayWindowInsetsController.aidl
@@ -0,0 +1,48 @@
+/**
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+
+/**
+ * Singular controller of insets to use when there isn't another obvious controller available.
+ * Specifically, this will take over insets control in multi-window.
+ * @hide
+ */
+oneway interface IDisplayWindowInsetsController {
+
+    /**
+     * @see IWindow#insetsChanged
+     */
+    void insetsChanged(in InsetsState insetsState);
+
+    /**
+     * @see IWindow#insetsControlChanged
+     */
+    void insetsControlChanged(in InsetsState insetsState, in InsetsSourceControl[] activeControls);
+
+    /**
+     * @see IWindow#showInsets
+     */
+    void showInsets(int types, boolean fromIme);
+
+    /**
+     * @see IWindow#hideInsets
+     */
+    void hideInsets(int types, boolean fromIme);
+}
diff --git a/core/java/android/view/ITaskOrganizer.aidl b/core/java/android/view/ITaskOrganizer.aidl
new file mode 100644
index 0000000..e92aafe
--- /dev/null
+++ b/core/java/android/view/ITaskOrganizer.aidl
@@ -0,0 +1,38 @@
+/* //device/java/android/android/view/ITaskOrganizer.aidl
+**
+** Copyright 2019, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.view;
+
+import android.view.IWindowContainer;
+import android.view.SurfaceControl;
+import android.app.ActivityManager;
+
+/**
+ * Interface for ActivityTaskManager/WindowManager to delegate control of tasks.
+ * {@hide}
+ */
+oneway interface ITaskOrganizer {
+    void taskAppeared(in IWindowContainer container,
+        in ActivityManager.RunningTaskInfo taskInfo);
+    void taskVanished(in IWindowContainer container);
+
+    /**
+     * Called upon completion of
+     * ActivityTaskManagerService#applyTaskOrganizerTransaction
+     */
+    void transactionReady(int id, in SurfaceControl.Transaction t);
+}
\ No newline at end of file
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 9496827..993bdc4 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -35,6 +35,7 @@
 import android.view.IApplicationToken;
 import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IDockedStackListener;
+import android.view.IDisplayWindowInsetsController;
 import android.view.IDisplayWindowListener;
 import android.view.IDisplayFoldListener;
 import android.view.IDisplayWindowRotationController;
@@ -49,6 +50,7 @@
 import android.view.IWindowSessionCallback;
 import android.view.KeyEvent;
 import android.view.InputEvent;
+import android.view.InsetsState;
 import android.view.MagnificationSpec;
 import android.view.MotionEvent;
 import android.view.InputChannel;
@@ -711,4 +713,16 @@
      * @return true if the display was successfully mirrored.
      */
     boolean mirrorDisplay(int displayId, out SurfaceControl outSurfaceControl);
+
+    /**
+     * When in multi-window mode, the provided displayWindowInsetsController will control insets
+     * animations.
+     */
+    void setDisplayWindowInsetsController(
+            int displayId, in IDisplayWindowInsetsController displayWindowInsetsController);
+
+    /**
+     * Called when a remote process modifies insets on a display window container.
+     */
+    void modifyDisplayWindowInsets(int displayId, in InsetsState state);
 }
diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java
index 6fdadc6..27edb0b 100644
--- a/core/java/android/view/InsetsAnimationControlCallbacks.java
+++ b/core/java/android/view/InsetsAnimationControlCallbacks.java
@@ -16,17 +16,28 @@
 
 package android.view;
 
+import android.view.InsetsController.LayoutInsetsDuringAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
+
 /**
  * Provide an interface to let InsetsAnimationControlImpl call back into its owner.
  * @hide
  */
 public interface InsetsAnimationControlCallbacks {
+
     /**
-     * Dispatch the animation started event to all listeners.
-     * @param animation
+     * Executes the necessary code to start the animation in the correct order, including:
+     * <ul>
+     *     <li>Dispatch {@link WindowInsetsAnimationCallback#onPrepare}</li>
+     *     <li>Update insets state and run layout according to {@code layoutDuringAnimation}</li>
+     *     <li>Dispatch {@link WindowInsetsAnimationCallback#onStart}</li>
+     *     <li>Dispatch {@link WindowInsetsAnimationControlListener#onReady}</li>
+     * </ul>
      */
-    void dispatchAnimationStarted(WindowInsetsAnimationCallback.InsetsAnimation animation,
-            WindowInsetsAnimationCallback.AnimationBounds bounds);
+    void startAnimation(InsetsAnimationControlImpl controller,
+            WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation,
+            AnimationBounds bounds, @LayoutInsetsDuringAnimation int layoutDuringAnimation);
 
     /**
      * Schedule the apply by posting the animation callback.
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 771695c..6589e75 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 import static android.view.InsetsState.ISIDE_BOTTOM;
 import static android.view.InsetsState.ISIDE_FLOATING;
 import static android.view.InsetsState.ISIDE_LEFT;
@@ -30,6 +32,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.SparseSetArray;
+import android.view.InsetsController.LayoutInsetsDuringAnimation;
 import android.view.InsetsState.InternalInsetsSide;
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.WindowInsets.Type.InsetsType;
@@ -80,7 +83,8 @@
     public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, Rect frame,
             InsetsState state, WindowInsetsAnimationControlListener listener,
             @InsetsType int types,
-            InsetsAnimationControlCallbacks controller, long durationMs, boolean fade) {
+            InsetsAnimationControlCallbacks controller, long durationMs, boolean fade,
+            @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) {
         mControls = controls;
         mListener = listener;
         mTypes = types;
@@ -95,14 +99,11 @@
         mFrame = new Rect(frame);
         buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mControls);
 
-        // TODO: Check for controllability first and wait for IME if needed.
-        listener.onReady(this, types);
-
         mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes,
                 InsetsController.INTERPOLATOR, durationMs);
         mAnimation.setAlpha(getCurrentAlpha());
-        mController.dispatchAnimationStarted(mAnimation,
-                new AnimationBounds(mHiddenInsets, mShownInsets));
+        mController.startAnimation(this, listener, types, mAnimation,
+                new AnimationBounds(mHiddenInsets, mShownInsets), layoutInsetsDuringAnimation);
     }
 
     @Override
@@ -257,10 +258,6 @@
         for (int i = items.size() - 1; i >= 0; i--) {
             final InsetsSourceControl control = items.valueAt(i);
             final InsetsSource source = mInitialInsetsState.getSource(control.getType());
-            if (control == null) {
-                // TODO: remove this check when we ensure the elements will not be null.
-                continue;
-            }
             final SurfaceControl leash = control.getLeash();
 
             mTmpMatrix.setTranslate(control.getSurfacePosition().x, control.getSurfacePosition().y);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 4d4ace27c..775490c 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -37,16 +37,20 @@
 import android.view.InsetsSourceConsumer.ShowResult;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.SurfaceControl.Transaction;
+import android.view.ViewTreeObserver.OnPreDrawListener;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimationCallback.AnimationBounds;
 import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 
 /**
@@ -67,6 +71,37 @@
     private @interface AnimationDirection{}
 
     /**
+     * Layout mode during insets animation: The views should be laid out as if the changing inset
+     * types are fully shown. Before starting the animation, {@link View#onApplyWindowInsets} will
+     * be called as if the changing insets types are shown, which will result in the views being
+     * laid out as if the insets are fully shown.
+     */
+    static final int LAYOUT_INSETS_DURING_ANIMATION_SHOWN = 0;
+
+    /**
+     * Layout mode during insets animation: The views should be laid out as if the changing inset
+     * types are fully hidden. Before starting the animation, {@link View#onApplyWindowInsets} will
+     * be called as if the changing insets types are hidden, which will result in the views being
+     * laid out as if the insets are fully hidden.
+     */
+    static final int LAYOUT_INSETS_DURING_ANIMATION_HIDDEN = 1;
+
+    /**
+     * Determines the behavior of how the views should be laid out during an insets animation that
+     * is controlled by the application by calling {@link #controlWindowInsetsAnimation}.
+     * <p>
+     * When the animation is system-initiated, the layout mode is always chosen such that the
+     * pre-animation layout will represent the opposite of the starting state, i.e. when insets
+     * are appearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_SHOWN} will be used. When insets
+     * are disappearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_HIDDEN} will be used.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {LAYOUT_INSETS_DURING_ANIMATION_SHOWN,
+            LAYOUT_INSETS_DURING_ANIMATION_HIDDEN})
+    @interface LayoutInsetsDuringAnimation {
+    }
+
+    /**
      * Translation animation evaluator.
      */
     private static TypeEvaluator<Insets> sEvaluator = (fraction, startValue, endValue) -> Insets.of(
@@ -109,11 +144,7 @@
         @Override
         public void onReady(WindowInsetsAnimationController controller, int types) {
             mController = controller;
-            if (mShow) {
-                showDirectly(types);
-            } else {
-                hideDirectly(types);
-            }
+
             mAnimationDirection = mShow ? DIRECTION_SHOW : DIRECTION_HIDE;
             mAnimator = ObjectAnimator.ofObject(
                     controller,
@@ -131,7 +162,9 @@
                     onAnimationFinish();
                 }
             });
+            mStartingAnimation = true;
             mAnimator.start();
+            mStartingAnimation = false;
         }
 
         @Override
@@ -185,6 +218,7 @@
     private int mPendingTypesToShow;
 
     private int mLastLegacySoftInputMode;
+    private boolean mStartingAnimation;
 
     private SyncRtSurfaceTransactionApplier mApplier;
 
@@ -266,6 +300,14 @@
     }
 
     /**
+     * @see InsetsState#calculateVisibleInsets(Rect, Rect, int)
+     */
+    public Rect calculateVisibleInsets(Rect legacyVisibleInsets,
+            @SoftInputModeFlags int softInputMode) {
+        return mState.calculateVisibleInsets(mFrame, legacyVisibleInsets, softInputMode);
+    }
+
+    /**
      * Called when the server has dispatched us a new set of inset controls.
      */
     public void onControlsChanged(InsetsSourceControl[] activeControls) {
@@ -312,7 +354,7 @@
                 // Only one animator (with multiple InsetsType) can run at a time.
                 // previous one should be cancelled for simplicity.
                 cancelExistingAnimation();
-            } else if (consumer.isVisible()
+            } else if (consumer.isRequestedVisible()
                     && (mAnimationDirection == DIRECTION_NONE
                     || mAnimationDirection == DIRECTION_HIDE)) {
                 // no-op: already shown or animating in (because window visibility is
@@ -338,7 +380,7 @@
             InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
             if (mAnimationDirection == DIRECTION_SHOW) {
                 cancelExistingAnimation();
-            } else if (!consumer.isVisible()
+            } else if (!consumer.isRequestedVisible()
                     && (mAnimationDirection == DIRECTION_NONE
                     || mAnimationDirection == DIRECTION_HIDE)) {
                 // no-op: already hidden or animating out.
@@ -363,20 +405,21 @@
             listener.onCancelled();
             return;
         }
-        controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */);
+        controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */,
+                getLayoutInsetsDuringAnimationMode(types));
     }
 
     private void controlAnimationUnchecked(@InsetsType int types,
             WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
-            long durationMs, boolean fade) {
+            long durationMs, boolean fade,
+            @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) {
         if (types == 0) {
             // nothing to animate.
             return;
         }
         cancelExistingControllers(types);
 
-        final ArraySet<Integer> internalTypes = mState.toInternalType(types);
-        final SparseArray<InsetsSourceConsumer> consumers = new SparseArray<>();
+        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
         final SparseArray<InsetsSourceControl> controls = new SparseArray<>();
 
         Pair<Integer, Boolean> typesReadyPair = collectSourceControls(
@@ -399,7 +442,8 @@
         }
 
         final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(controls,
-                frame, mState, listener, typesReady, this, durationMs, fade);
+                frame, mState, listener, typesReady, this, durationMs, fade,
+                layoutInsetsDuringAnimation);
         mAnimationControls.add(controller);
     }
 
@@ -413,7 +457,7 @@
         boolean isReady = true;
         for (int i = internalTypes.size() - 1; i >= 0; i--) {
             InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
-            boolean setVisible = !consumer.isVisible();
+            boolean setVisible = !consumer.isRequestedVisible();
             if (setVisible) {
                 // Show request
                 switch(consumer.requestShow(fromIme)) {
@@ -441,7 +485,10 @@
                 }
                 typesReady |= InsetsState.toPublicType(consumer.getType());
             }
-            controls.put(consumer.getType(), consumer.getControl());
+            final InsetsSourceControl control = consumer.getControl();
+            if (control != null) {
+                controls.put(consumer.getType(), control);
+            }
         }
         return new Pair<>(typesReady, isReady);
     }
@@ -452,6 +499,29 @@
         return typesReady;
     }
 
+    private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode(
+            @InsetsType int types) {
+
+        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+
+        // Generally, we want to layout the opposite of the current state. This is to make animation
+        // callbacks easy to use: The can capture the layout values and then treat that as end-state
+        // during the animation.
+        //
+        // However, if controlling multiple sources, we want to treat it as shown if any of the
+        // types is currently hidden.
+        for (int i = internalTypes.size() - 1; i >= 0; i--) {
+            InsetsSourceConsumer consumer = mSourceConsumers.get(internalTypes.valueAt(i));
+            if (consumer == null) {
+                continue;
+            }
+            if (!consumer.isRequestedVisible()) {
+                return LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
+            }
+        }
+        return LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
+    }
+
     private void cancelExistingControllers(@InsetsType int types) {
         for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
             InsetsAnimationControlImpl control = mAnimationControls.get(i);
@@ -595,7 +665,9 @@
         // and hidden state insets are correct.
         controlAnimationUnchecked(
                 types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(),
-                true /* fade */);
+                true /* fade */, show
+                        ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
+                        : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN);
     }
 
     private void hideDirectly(@InsetsType int types) {
@@ -627,18 +699,40 @@
 
     @VisibleForTesting
     @Override
-    public void dispatchAnimationStarted(InsetsAnimation animation, AnimationBounds bounds) {
-        mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation, bounds);
+    public void startAnimation(InsetsAnimationControlImpl controller,
+            WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation,
+            AnimationBounds bounds, int layoutDuringAnimation) {
+        if (layoutDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) {
+            showDirectly(types);
+        } else {
+            hideDirectly(types);
+        }
+        mViewRoot.mView.dispatchWindowInsetsAnimationPrepare(animation);
+        mViewRoot.mView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                mViewRoot.mView.getViewTreeObserver().removeOnPreDrawListener(this);
+                mViewRoot.mView.dispatchWindowInsetsAnimationStart(animation, bounds);
+                listener.onReady(controller, types);
+                return true;
+            }
+        });
+        mViewRoot.mView.invalidate();
     }
 
     @VisibleForTesting
     public void dispatchAnimationFinished(InsetsAnimation animation) {
-        mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
+        mViewRoot.mView.dispatchWindowInsetsAnimationFinish(animation);
     }
 
     @VisibleForTesting
     @Override
     public void scheduleApplyChangeInsets() {
+        if (mStartingAnimation) {
+            mAnimCallback.run();
+            mAnimCallbackScheduled = false;
+            return;
+        }
         if (!mAnimCallbackScheduled) {
             mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION,
                     mAnimCallback, null /* token*/);
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 324d562..67ccfd6 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.Nullable;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Parcel;
@@ -23,6 +24,7 @@
 import android.view.InsetsState.InternalInsetsType;
 
 import java.io.PrintWriter;
+import java.util.Objects;
 
 /**
  * Represents the state of a single window generating insets for clients.
@@ -34,6 +36,7 @@
 
     /** Frame of the source in screen coordinate space */
     private final Rect mFrame;
+    private @Nullable Rect mVisibleFrame;
     private boolean mVisible;
 
     private final Rect mTmpFrame = new Rect();
@@ -54,6 +57,10 @@
         mFrame.set(frame);
     }
 
+    public void setVisibleFrame(@Nullable Rect visibleFrame) {
+        mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : visibleFrame;
+    }
+
     public void setVisible(boolean visible) {
         mVisible = visible;
     }
@@ -66,6 +73,10 @@
         return mFrame;
     }
 
+    public @Nullable Rect getVisibleFrame() {
+        return mVisibleFrame;
+    }
+
     public boolean isVisible() {
         return mVisible;
     }
@@ -79,10 +90,22 @@
      *         source.
      */
     public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) {
+        return calculateInsets(relativeFrame, mFrame, ignoreVisibility);
+    }
+
+    /**
+     * Like {@link #calculateInsets(Rect, boolean)}, but will return visible insets.
+     */
+    public Insets calculateVisibleInsets(Rect relativeFrame) {
+        return calculateInsets(relativeFrame, mVisibleFrame != null ? mVisibleFrame : mFrame,
+                false /* ignoreVisibility */);
+    }
+
+    private Insets calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility) {
         if (!ignoreVisibility && !mVisible) {
             return Insets.NONE;
         }
-        if (!mTmpFrame.setIntersect(mFrame, relativeFrame)) {
+        if (!mTmpFrame.setIntersect(frame, relativeFrame)) {
             return Insets.NONE;
         }
 
@@ -110,6 +133,9 @@
         pw.print(prefix);
         pw.print("InsetsSource type="); pw.print(InsetsState.typeToString(mType));
         pw.print(" frame="); pw.print(mFrame.toShortString());
+        if (mVisibleFrame != null) {
+            pw.print(" visibleFrmae="); pw.print(mVisibleFrame.toShortString());
+        }
         pw.print(" visible="); pw.print(mVisible);
         pw.println();
     }
@@ -123,6 +149,7 @@
 
         if (mType != that.mType) return false;
         if (mVisible != that.mVisible) return false;
+        if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
         return mFrame.equals(that.mFrame);
     }
 
@@ -137,6 +164,7 @@
     public InsetsSource(Parcel in) {
         mType = in.readInt();
         mFrame = in.readParcelable(null /* loader */);
+        mVisibleFrame = in.readParcelable(null /* loader */);
         mVisible = in.readBoolean();
     }
 
@@ -149,6 +177,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mType);
         dest.writeParcelable(mFrame, 0 /* flags*/);
+        dest.writeParcelable(mVisibleFrame, 0 /* flags */);
         dest.writeBoolean(mVisible);
     }
 
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index c6d9898..b2a5d91 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -53,7 +53,7 @@
     }
 
     protected final InsetsController mController;
-    protected boolean mVisible;
+    protected boolean mRequestedVisible;
     private final Supplier<Transaction> mTransactionSupplier;
     private final @InternalInsetsType int mType;
     private final InsetsState mState;
@@ -66,7 +66,7 @@
         mState = state;
         mTransactionSupplier = transactionSupplier;
         mController = controller;
-        mVisible = InsetsState.getDefaultVisibility(type);
+        mRequestedVisible = InsetsState.getDefaultVisibility(type);
     }
 
     public void setControl(@Nullable InsetsSourceControl control) {
@@ -94,12 +94,12 @@
 
     @VisibleForTesting
     public void show() {
-        setVisible(true);
+        setRequestedVisible(true);
     }
 
     @VisibleForTesting
     public void hide() {
-        setVisible(false);
+        setRequestedVisible(false);
     }
 
     /**
@@ -126,16 +126,16 @@
         if (mSourceControl == null) {
             return false;
         }
-        if (mState.getSource(mType).isVisible() == mVisible) {
+        if (mState.getSource(mType).isVisible() == mRequestedVisible) {
             return false;
         }
-        mState.getSource(mType).setVisible(mVisible);
+        mState.getSource(mType).setVisible(mRequestedVisible);
         return true;
     }
 
     @VisibleForTesting
-    public boolean isVisible() {
-        return mVisible;
+    public boolean isRequestedVisible() {
+        return mRequestedVisible;
     }
 
     /**
@@ -157,11 +157,15 @@
         // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
     }
 
-    private void setVisible(boolean visible) {
-        if (mVisible == visible) {
+    /**
+     * Sets requested visibility from the client, regardless of whether we are able to control it at
+     * the moment.
+     */
+    private void setRequestedVisible(boolean requestedVisible) {
+        if (mRequestedVisible == requestedVisible) {
             return;
         }
-        mVisible = visible;
+        mRequestedVisible = requestedVisible;
         applyLocalVisibilityOverride();
         mController.notifyVisibilityChanged();
     }
@@ -173,7 +177,7 @@
         }
 
         final Transaction t = mTransactionSupplier.get();
-        if (mVisible) {
+        if (mRequestedVisible) {
             t.show(mSourceControl.getLeash());
         } else {
             t.hide(mSourceControl.getLeash());
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index e3fed3a..e33ca70 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -19,10 +19,17 @@
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+import static android.view.ViewRootImpl.sNewInsetsMode;
+import static android.view.WindowInsets.Type.IME;
 import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
 import static android.view.WindowInsets.Type.SIZE;
 import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
+import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.indexOf;
+import static android.view.WindowInsets.Type.isVisibleInsetsType;
+import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
@@ -36,6 +43,7 @@
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowManager.LayoutParams;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -156,11 +164,10 @@
                     && source.getType() != ITYPE_IME;
             boolean skipSystemBars = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
                     && (type == ITYPE_STATUS_BAR || type == ITYPE_NAVIGATION_BAR);
-            boolean skipIme = source.getType() == ITYPE_IME
-                    && (legacySoftInputMode & LayoutParams.SOFT_INPUT_ADJUST_RESIZE) == 0;
             boolean skipLegacyTypes = ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
-                    && (toPublicType(type) & Type.compatSystemInsets()) != 0;
-            if (skipSystemBars || skipIme || skipLegacyTypes || skipNonImeInImeMode) {
+                    && (type == ITYPE_STATUS_BAR || type == ITYPE_NAVIGATION_BAR
+                            || type == ITYPE_IME);
+            if (skipSystemBars || skipLegacyTypes || skipNonImeInImeMode) {
                 typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible();
                 continue;
             }
@@ -175,8 +182,37 @@
                         typeMaxInsetsMap, null /* typeSideMap */, null /* typeVisibilityMap */);
             }
         }
+        final int softInputAdjustMode = legacySoftInputMode & SOFT_INPUT_MASK_ADJUST;
         return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
-                alwaysConsumeSystemBars, cutout);
+                alwaysConsumeSystemBars, cutout, softInputAdjustMode == SOFT_INPUT_ADJUST_RESIZE
+                        ? systemBars() | ime()
+                        : systemBars());
+    }
+
+    public Rect calculateVisibleInsets(Rect frame, Rect legacyVisibleInsets,
+            @SoftInputModeFlags int softInputMode) {
+        if (sNewInsetsMode == NEW_INSETS_MODE_NONE) {
+            return legacyVisibleInsets;
+        }
+
+        Insets insets = Insets.NONE;
+        for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+            InsetsSource source = mSources.get(type);
+            if (source == null) {
+                continue;
+            }
+            if (sNewInsetsMode != NEW_INSETS_MODE_FULL && type != ITYPE_IME) {
+                continue;
+            }
+
+            // Ignore everything that's not a system bar or IME.
+            int publicType = InsetsState.toPublicType(type);
+            if (!isVisibleInsetsType(publicType, softInputMode)) {
+                continue;
+            }
+            insets = Insets.max(source.calculateVisibleInsets(frame), insets);
+        }
+        return insets.toRect();
     }
 
     private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index f6d6522..ff8455a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -677,6 +677,22 @@
         }
 
         /**
+         * Set the initial visibility for the SurfaceControl.
+         *
+         * @param hidden Whether the Surface is initially HIDDEN.
+         * @hide
+         */
+        @NonNull
+        public Builder setHidden(boolean hidden) {
+            if (hidden) {
+                mFlags |= HIDDEN;
+            } else {
+                mFlags &= ~HIDDEN;
+            }
+            return this;
+        }
+
+        /**
          * Set a parent surface for our new SurfaceControl.
          *
          * Child surfaces are constrained to the onscreen region of their parent.
@@ -789,8 +805,7 @@
      * @param name     The surface name, must not be null.
      * @param w        The surface initial width.
      * @param h        The surface initial height.
-     * @param flags    The surface creation flags.  Should always include {@link #HIDDEN}
-     *                 in the creation flags.
+     * @param flags    The surface creation flags.
      * @param metadata Initial metadata.
      * @throws throws OutOfResourcesException If the SurfaceControl cannot be created.
      */
@@ -801,15 +816,6 @@
             throw new IllegalArgumentException("name must not be null");
         }
 
-        if ((flags & SurfaceControl.HIDDEN) == 0) {
-            Log.w(TAG, "Surfaces should always be created with the HIDDEN flag set "
-                    + "to ensure that they are not made visible prematurely before "
-                    + "all of the surface's properties have been configured.  "
-                    + "Set the other properties and make the surface visible within "
-                    + "a transaction.  New surface name: " + name,
-                    new Throwable());
-        }
-
         mName = name;
         mWidth = w;
         mHeight = h;
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 52ea2b2..1782544 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -379,7 +379,7 @@
                  * This gets called on a RenderThread worker thread, so members accessed here must
                  * be protected by a lock.
                  */
-                final boolean useBLAST = ViewRootImpl.USE_BLAST_BUFFERQUEUE;
+                final boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER;
                 viewRoot.registerRtFrameCallback(frame -> {
                     try {
                         final SurfaceControl.Transaction t = useBLAST ?
@@ -1107,7 +1107,7 @@
 
     private void applySurfaceTransforms(SurfaceControl surface, SurfaceControl.Transaction t,
             Rect position, long frameNumber) {
-        if (frameNumber > 0 && ViewRootImpl.USE_BLAST_BUFFERQUEUE == false) {
+        if (frameNumber > 0 && !WindowManagerGlobal.USE_BLAST_ADAPTER) {
             final ViewRootImpl viewRoot = getViewRootImpl();
 
             t.deferTransactionUntilSurface(surface, viewRoot.mSurface,
@@ -1125,7 +1125,7 @@
     }
 
     private void setParentSpaceRectangle(Rect position, long frameNumber) {
-        final boolean useBLAST = ViewRootImpl.USE_BLAST_BUFFERQUEUE;
+        final boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER;
         final ViewRootImpl viewRoot = getViewRootImpl();
         final SurfaceControl.Transaction t = useBLAST ? viewRoot.getBLASTSyncTransaction() :
             mRtTransaction;
@@ -1186,7 +1186,7 @@
 
         @Override
         public void positionLost(long frameNumber) {
-            boolean useBLAST = ViewRootImpl.USE_BLAST_BUFFERQUEUE;
+            boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER;
             if (DEBUG) {
                 Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
                         System.identityHashCode(this), frameNumber));
@@ -1524,7 +1524,7 @@
     @Override
     public void invalidate(boolean invalidateCache) {
         super.invalidate(invalidateCache);
-        if (ViewRootImpl.USE_BLAST_BUFFERQUEUE == false) {
+        if (!WindowManagerGlobal.USE_BLAST_ADAPTER) {
             return;
         }
         final ViewRootImpl viewRoot = getViewRootImpl();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0db80e2..13d609b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -11117,7 +11117,21 @@
     }
 
     /**
-     * Dispatches {@link WindowInsetsAnimationCallback#onStarted(InsetsAnimation, AnimationBounds)}
+     * Dispatches {@link WindowInsetsAnimationCallback#onPrepare(InsetsAnimation)}
+     * when Window Insets animation is being prepared.
+     * @param animation current animation
+     *
+     * @see WindowInsetsAnimationCallback#onPrepare(InsetsAnimation)
+     */
+    public void dispatchWindowInsetsAnimationPrepare(
+            @NonNull InsetsAnimation animation) {
+        if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+            mListenerInfo.mWindowInsetsAnimationCallback.onPrepare(animation);
+        }
+    }
+
+    /**
+     * Dispatches {@link WindowInsetsAnimationCallback#onStart(InsetsAnimation, AnimationBounds)}
      * when Window Insets animation is started.
      * @param animation current animation
      * @param bounds the upper and lower {@link AnimationBounds} that provides range of
@@ -11125,10 +11139,10 @@
      * @return the upper and lower {@link AnimationBounds}.
      */
     @NonNull
-    public AnimationBounds dispatchWindowInsetsAnimationStarted(
+    public AnimationBounds dispatchWindowInsetsAnimationStart(
             @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
         if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
-            return mListenerInfo.mWindowInsetsAnimationCallback.onStarted(animation, bounds);
+            return mListenerInfo.mWindowInsetsAnimationCallback.onStart(animation, bounds);
         }
         return bounds;
     }
@@ -11149,13 +11163,13 @@
     }
 
     /**
-     * Dispatches {@link WindowInsetsAnimationCallback#onFinished(InsetsAnimation)}
+     * Dispatches {@link WindowInsetsAnimationCallback#onFinish(InsetsAnimation)}
      * when Window Insets animation finishes.
      * @param animation The current ongoing {@link InsetsAnimation}.
      */
-    public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
+    public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) {
         if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
-            mListenerInfo.mWindowInsetsAnimationCallback.onFinished(animation);
+            mListenerInfo.mWindowInsetsAnimationCallback.onFinish(animation);
         }
     }
 
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 5fb7177..047d7da 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -7199,13 +7199,23 @@
     }
 
     @Override
-    @NonNull
-    public AnimationBounds dispatchWindowInsetsAnimationStarted(
-            @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
-        super.dispatchWindowInsetsAnimationStarted(animation, bounds);
+    public void dispatchWindowInsetsAnimationPrepare(
+            @NonNull InsetsAnimation animation) {
+        super.dispatchWindowInsetsAnimationPrepare(animation);
         final int count = getChildCount();
         for (int i = 0; i < count; i++) {
-            getChildAt(i).dispatchWindowInsetsAnimationStarted(animation, bounds);
+            getChildAt(i).dispatchWindowInsetsAnimationPrepare(animation);
+        }
+    }
+
+    @Override
+    @NonNull
+    public AnimationBounds dispatchWindowInsetsAnimationStart(
+            @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
+        super.dispatchWindowInsetsAnimationStart(animation, bounds);
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            getChildAt(i).dispatchWindowInsetsAnimationStart(animation, bounds);
         }
         return bounds;
     }
@@ -7222,11 +7232,11 @@
     }
 
     @Override
-    public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
-        super.dispatchWindowInsetsAnimationFinished(animation);
+    public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) {
+        super.dispatchWindowInsetsAnimationFinish(animation);
         final int count = getChildCount();
         for (int i = 0; i < count; i++) {
-            getChildAt(i).dispatchWindowInsetsAnimationFinished(animation);
+            getChildAt(i).dispatchWindowInsetsAnimationFinish(animation);
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 522ff9a..ca8ba4c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -192,11 +192,6 @@
     private static final boolean MT_RENDERER_AVAILABLE = true;
 
     /**
-     * @hide
-     */
-    public static final boolean USE_BLAST_BUFFERQUEUE = false;
-
-    /**
      * If set to 2, the view system will switch from using rectangles retrieved from window to
      * dispatch to the view hierarchy to using {@link InsetsController}, that derives the insets
      * directly from the full configuration, enabling richer information about the insets state, as
@@ -1312,7 +1307,7 @@
             }
             mWindowAttributes.privateFlags |= compatibleWindowFlag;
 
-            if (USE_BLAST_BUFFERQUEUE) {
+            if (WindowManagerGlobal.USE_BLAST_ADAPTER) {
                 mWindowAttributes.privateFlags =
                     WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
             }
@@ -1469,6 +1464,7 @@
             return;
         }
         mApplyInsetsRequested = true;
+        requestLayout();
 
         // If this changes during traversal, no need to schedule another one as it will dispatch it
         // during the current traversal.
@@ -2107,6 +2103,12 @@
         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
 
+    private void updateVisibleInsets() {
+        Rect visibleInsets = mInsetsController.calculateVisibleInsets(mPendingVisibleInsets,
+                mWindowAttributes.softInputMode);
+        mAttachInfo.mVisibleInsets.set(visibleInsets);
+    }
+
     InsetsController getInsetsController() {
         return mInsetsController;
     }
@@ -2255,7 +2257,7 @@
                     insetsChanged = true;
                 }
                 if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
-                    mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+                    updateVisibleInsets();
                     if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
                             + mAttachInfo.mVisibleInsets);
                 }
@@ -2321,6 +2323,7 @@
 
         if (mApplyInsetsRequested) {
             mApplyInsetsRequested = false;
+            updateVisibleInsets();
             dispatchApplyInsets(host);
             if (mLayoutRequested) {
                 // Short-circuit catching a new layout request here, so
@@ -2505,7 +2508,7 @@
                     contentInsetsChanged = true;
                 }
                 if (visibleInsetsChanged) {
-                    mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+                    updateVisibleInsets();
                     if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
                             + mAttachInfo.mVisibleInsets);
                 }
@@ -7273,7 +7276,7 @@
                 mPendingStableInsets, mPendingBackDropFrame, mPendingDisplayCutout,
                 mPendingMergedConfiguration, mSurfaceControl, mTempInsets);
         if (mSurfaceControl.isValid()) {
-            if (USE_BLAST_BUFFERQUEUE == false) {
+            if (!WindowManagerGlobal.USE_BLAST_ADAPTER) {
                 mSurface.copyFrom(mSurfaceControl);
             } else {
                 mSurface.transferFrom(getOrCreateBLASTSurface(
diff --git a/core/java/android/view/WindowContainerTransaction.java b/core/java/android/view/WindowContainerTransaction.java
index 607a870..253794f 100644
--- a/core/java/android/view/WindowContainerTransaction.java
+++ b/core/java/android/view/WindowContainerTransaction.java
@@ -62,6 +62,18 @@
         return this;
     }
 
+    /**
+     * Notify activies within the hiearchy of a container that they have entered picture-in-picture
+     * mode with the given bounds.
+     */
+    public WindowContainerTransaction scheduleFinishEnterPip(IWindowContainer container,
+            Rect bounds) {
+        Change chg = getOrCreateChange(container.asBinder());
+        chg.mSchedulePipCallback = true;
+        chg.mPinnedBounds = new Rect(bounds);
+        return this;
+    }
+
     public Map<IBinder, Change> getChanges() {
         return mChanges;
     }
@@ -104,12 +116,20 @@
         private @ActivityInfo.Config int mConfigSetMask = 0;
         private @WindowConfiguration.WindowConfig int mWindowSetMask = 0;
 
+        private boolean mSchedulePipCallback = false;
+        private Rect mPinnedBounds = null;
+
         public Change() {}
 
         protected Change(Parcel in) {
             mConfiguration.readFromParcel(in);
             mConfigSetMask = in.readInt();
             mWindowSetMask = in.readInt();
+            mSchedulePipCallback = (in.readInt() != 0);
+            if (mSchedulePipCallback ) {
+                mPinnedBounds = new Rect();
+                mPinnedBounds.readFromParcel(in);
+            }
         }
 
         public Configuration getConfiguration() {
@@ -126,6 +146,14 @@
             return mWindowSetMask;
         }
 
+        /**
+         * Returns the bounds to be used for scheduling the enter pip callback
+         * or null if no callback is to be scheduled.
+         */
+        public Rect getEnterPipBounds() {
+            return mPinnedBounds;
+        }
+
         @Override
         public String toString() {
             final boolean changesBounds =
@@ -151,6 +179,11 @@
             mConfiguration.writeToParcel(dest, flags);
             dest.writeInt(mConfigSetMask);
             dest.writeInt(mWindowSetMask);
+
+            dest.writeInt(mSchedulePipCallback ? 1 : 0);
+            if (mSchedulePipCallback ) {
+                mPinnedBounds.writeToParcel(dest, flags);
+            }
         }
 
         @Override
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index a9cc50f..9291b56 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -27,8 +27,10 @@
 import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
 import static android.view.WindowInsets.Type.TAPPABLE_ELEMENT;
 import static android.view.WindowInsets.Type.all;
-import static android.view.WindowInsets.Type.compatSystemInsets;
 import static android.view.WindowInsets.Type.indexOf;
+import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -40,6 +42,7 @@
 import android.graphics.Rect;
 import android.util.SparseArray;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethod;
 
@@ -87,6 +90,8 @@
     private final boolean mStableInsetsConsumed;
     private final boolean mDisplayCutoutConsumed;
 
+    private final int mCompatInsetTypes;
+
     /**
      * Since new insets may be added in the future that existing apps couldn't
      * know about, this fully empty constant shouldn't be made available to apps
@@ -112,7 +117,7 @@
             boolean isRound, boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) {
         this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect),
                 createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)),
-                isRound, alwaysConsumeSystemBars, displayCutout);
+                isRound, alwaysConsumeSystemBars, displayCutout, systemBars());
     }
 
     /**
@@ -131,7 +136,7 @@
             @Nullable Insets[] typeMaxInsetsMap,
             boolean[] typeVisibilityMap,
             boolean isRound,
-            boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) {
+            boolean alwaysConsumeSystemBars, DisplayCutout displayCutout, int compatInsetTypes) {
         mSystemWindowInsetsConsumed = typeInsetsMap == null;
         mTypeInsetsMap = mSystemWindowInsetsConsumed
                 ? new Insets[SIZE]
@@ -145,6 +150,7 @@
         mTypeVisibilityMap = typeVisibilityMap;
         mIsRound = isRound;
         mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+        mCompatInsetTypes = compatInsetTypes;
 
         mDisplayCutoutConsumed = displayCutout == null;
         mDisplayCutout = (mDisplayCutoutConsumed || displayCutout.isEmpty())
@@ -160,7 +166,8 @@
         this(src.mSystemWindowInsetsConsumed ? null : src.mTypeInsetsMap,
                 src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap,
                 src.mTypeVisibilityMap, src.mIsRound,
-                src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src));
+                src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src),
+                src.mCompatInsetTypes);
     }
 
     private static DisplayCutout displayCutoutCopyConstructorArgument(WindowInsets w) {
@@ -211,7 +218,8 @@
     /** @hide */
     @UnsupportedAppUsage
     public WindowInsets(Rect systemWindowInsets) {
-        this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null);
+        this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null,
+                systemBars());
     }
 
     /**
@@ -280,7 +288,7 @@
      */
     @NonNull
     public Insets getSystemWindowInsets() {
-        return getInsets(mTypeInsetsMap, compatSystemInsets());
+        return getInsets(mTypeInsetsMap, mCompatInsetTypes);
     }
 
     /**
@@ -439,7 +447,8 @@
                 mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
                 mTypeVisibilityMap,
                 mIsRound, mAlwaysConsumeSystemBars,
-                null /* displayCutout */);
+                null /* displayCutout */,
+                mCompatInsetTypes);
     }
 
 
@@ -485,7 +494,8 @@
         return new WindowInsets(null, mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
                 mTypeVisibilityMap,
                 mIsRound, mAlwaysConsumeSystemBars,
-                displayCutoutCopyConstructorArgument(this));
+                displayCutoutCopyConstructorArgument(this),
+                mCompatInsetTypes);
     }
 
     // TODO(b/119190588): replace @code with @link below
@@ -555,7 +565,7 @@
      */
     @NonNull
     public Insets getStableInsets() {
-        return getInsets(mTypeMaxInsetsMap, compatSystemInsets());
+        return getInsets(mTypeMaxInsetsMap, mCompatInsetTypes);
     }
 
     /**
@@ -733,7 +743,8 @@
     public WindowInsets consumeStableInsets() {
         return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap, null,
                 mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars,
-                displayCutoutCopyConstructorArgument(this));
+                displayCutoutCopyConstructorArgument(this),
+                mCompatInsetTypes);
     }
 
     /**
@@ -817,7 +828,8 @@
                         ? null
                         : mDisplayCutout == null
                                 ? DisplayCutout.NO_CUTOUT
-                                : mDisplayCutout.inset(left, top, right, bottom));
+                                : mDisplayCutout.inset(left, top, right, bottom),
+                mCompatInsetTypes);
     }
 
     @Override
@@ -1134,7 +1146,8 @@
         public WindowInsets build() {
             return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
                     mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
-                    mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout);
+                    mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout,
+                    systemBars());
         }
     }
 
@@ -1271,15 +1284,6 @@
         }
 
         /**
-         * @return Inset types representing the list of bars that traditionally were denoted as
-         *         system insets.
-         * @hide
-         */
-        static @InsetsType int compatSystemInsets() {
-            return STATUS_BARS | NAVIGATION_BARS | IME;
-        }
-
-        /**
          * @return All inset types combined.
          *
          * TODO: Figure out if this makes sense at all, mixing e.g {@link #systemGestures()} and
@@ -1288,6 +1292,17 @@
         public static @InsetsType int all() {
             return 0xFFFFFFFF;
         }
+
+        /**
+         * Checks whether the specified type is considered to be part of visible insets.
+         * @hide
+         */
+        public static boolean isVisibleInsetsType(int type,
+                @SoftInputModeFlags int softInputModeFlags) {
+            int softInputMode = softInputModeFlags & SOFT_INPUT_MASK_ADJUST;
+            return (type & Type.systemBars()) != 0
+                    || (softInputMode != SOFT_INPUT_ADJUST_NOTHING && (type & Type.ime()) != 0);
+        }
     }
 
     /**
diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java
index 5e71f27..e84c3e3 100644
--- a/core/java/android/view/WindowInsetsAnimationCallback.java
+++ b/core/java/android/view/WindowInsetsAnimationCallback.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Insets;
+import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.animation.Interpolator;
 
@@ -30,7 +31,47 @@
 public interface WindowInsetsAnimationCallback {
 
     /**
-     * Called when an inset animation gets started.
+     * Called when an insets animation is about to start and before the views have been laid out in
+     * the end state of the animation. The ordering of events during an insets animation is the
+     * following:
+     * <p>
+     * <ul>
+     *     <li>Application calls {@link WindowInsetsController#hideInputMethod()},
+     *     {@link WindowInsetsController#showInputMethod()},
+     *     {@link WindowInsetsController#controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)}</li>
+     *     <li>onPrepare is called on the view hierarchy listeners</li>
+     *     <li>{@link View#onApplyWindowInsets} will be called with the end state of the
+     *     animation</li>
+     *     <li>View hierarchy gets laid out according to the changes the application has requested
+     *     due to the new insets being dispatched</li>
+     *     <li>{@link #onStart} is called <em>before</em> the view
+     *     hierarchy gets drawn in the new laid out state</li>
+     *     <li>{@link #onProgress} is called immediately after with the animation start state</li>
+     *     <li>The frame gets drawn.</li>
+     * </ul>
+     * <p>
+     * This ordering allows the application to inspect the end state after the animation has
+     * finished, and then revert to the starting state of the animation in the first
+     * {@link #onProgress} callback by using post-layout view properties like {@link View#setX} and
+     * related methods.
+     * <p>
+     * Note: If the animation is application controlled by using
+     * {@link WindowInsetsController#controlInputMethodAnimation}, the end state of the animation
+     * is undefined as the application may decide on the end state only by passing in the
+     * {@code shown} parameter when calling {@link WindowInsetsAnimationController#finish}. In this
+     * situation, the system will dispatch the insets in the opposite visibility state before the
+     * animation starts. Example: When controlling the input method with
+     * {@link WindowInsetsController#controlInputMethodAnimation} and the input method is currently
+     * showing, {@link View#onApplyWindowInsets} will receive a {@link WindowInsets} instance for
+     * which {@link WindowInsets#isVisible} will return {@code false} for {@link Type#ime}.
+     *
+     * @param animation The animation that is about to start.
+     */
+    default void onPrepare(@NonNull InsetsAnimation animation) {
+    }
+
+    /**
+     * Called when an insets animation gets started.
      * <p>
      * Note that, like {@link #onProgress}, dispatch of the animation start event is hierarchical:
      * It will starts at the root of the view hierarchy and then traverse it and invoke the callback
@@ -45,7 +86,7 @@
      *         subtree of the hierarchy.
      */
     @NonNull
-    default AnimationBounds onStarted(
+    default AnimationBounds onStart(
             @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
         return bounds;
     }
@@ -72,12 +113,12 @@
     WindowInsets onProgress(@NonNull WindowInsets insets);
 
     /**
-     * Called when an inset animation has finished.
+     * Called when an insets animation has finished.
      *
      * @param animation The animation that has finished running. This will be the same instance as
-     *                  passed into {@link #onStarted}
+     *                  passed into {@link #onStart}
      */
-    default void onFinished(@NonNull InsetsAnimation animation) {
+    default void onFinish(@NonNull InsetsAnimation animation) {
     }
 
     /**
@@ -253,14 +294,14 @@
 
         /**
          * Insets both the lower and upper bound by the specified insets. This is to be used in
-         * {@link WindowInsetsAnimationCallback#onStarted} to indicate that a part of the insets has
+         * {@link WindowInsetsAnimationCallback#onStart} to indicate that a part of the insets has
          * been used to offset or clip its children, and the children shouldn't worry about that
          * part anymore.
          *
          * @param insets The amount to inset.
          * @return A copy of this instance inset in the given directions.
          * @see WindowInsets#inset
-         * @see WindowInsetsAnimationCallback#onStarted
+         * @see WindowInsetsAnimationCallback#onStart
          */
         @NonNull
         public AnimationBounds inset(@NonNull Insets insets) {
diff --git a/core/java/android/view/WindowInsetsAnimationControlListener.java b/core/java/android/view/WindowInsetsAnimationControlListener.java
index 8a226c1..f91254d 100644
--- a/core/java/android/view/WindowInsetsAnimationControlListener.java
+++ b/core/java/android/view/WindowInsetsAnimationControlListener.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.Hide;
 import android.annotation.NonNull;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.inputmethod.EditorInfo;
@@ -26,6 +27,12 @@
 public interface WindowInsetsAnimationControlListener {
 
     /**
+     * @hide
+     */
+    default void onPrepare(int types) {
+    }
+
+    /**
      * Called when the animation is ready to be controlled. This may be delayed when the IME needs
      * to redraw because of an {@link EditorInfo} change, or when the window is starting up.
      *
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index 6de56be..9d7f292 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -149,7 +149,8 @@
      *
      * @param types The {@link InsetsType}s the application has requested to control.
      * @param durationMillis duration of animation in
-     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}
+     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the
+     *                       animation doesn't have a predetermined duration.
      * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
      *                 windows are ready to be controlled, among other callbacks.
      * @hide
@@ -162,7 +163,8 @@
      * modifying the position of the IME when it's causing insets.
      *
      * @param durationMillis duration of the animation in
-     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}
+     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the
+     *                       animation doesn't have a predetermined duration.
      * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
      *                 IME are ready to be controlled, among other callbacks.
      */
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 9578002..7d5564e 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -57,6 +57,12 @@
     private static final String TAG = "WindowManager";
 
     /**
+     * This flag controls whether ViewRootImpl will utilize the Blast Adapter
+     * to send buffer updates to SurfaceFlinger
+     */
+    public static final boolean USE_BLAST_ADAPTER = false;
+
+    /**
      * The user is navigating with keys (not the touch screen), so
      * navigational focus should be shown.
      */
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index ff31bcc..2e5a4b5 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -31,6 +31,7 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
+import android.app.RemoteAction;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -1209,6 +1210,61 @@
     }
 
     /**
+     * Register the provided {@link RemoteAction} with the given actionId
+     *
+     * @param action The remote action to be registered with the given actionId as system action.
+     * @param actionId The id uniquely identify the system action.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+    public void registerSystemAction(@NonNull RemoteAction action, int actionId) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.registerSystemAction(action, actionId);
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "System action " + action.getTitle() + " is registered.");
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error registering system action " + action.getTitle() + " ", re);
+        }
+    }
+
+   /**
+     * Unregister a system action with the given actionId
+     *
+     * @param actionId The id uniquely identify the system action to be unregistered.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+    public void unregisterSystemAction(int actionId) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return;
+            }
+        }
+        try {
+            service.unregisterSystemAction(actionId);
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "System action with actionId " + actionId + " is unregistered.");
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error unregistering system action with actionId " + actionId + " ", re);
+        }
+    }
+
+    /**
      * Notifies that the accessibility button in the system's navigation area has been clicked
      *
      * @param displayId The logical display id.
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 36515b3..392db57 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -16,6 +16,7 @@
 
 package android.view.accessibility;
 
+import android.app.RemoteAction;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.accessibilityservice.IAccessibilityServiceClient;
@@ -82,4 +83,7 @@
     int getAccessibilityWindowId(IBinder windowToken);
 
     long getRecommendedTimeoutMillis();
+
+    oneway void registerSystemAction(in RemoteAction action, int actionId);
+    oneway void unregisterSystemAction(int actionId);
 }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 67ce8d2..f3007a7 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -93,9 +93,12 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CancellationException;
-import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -429,7 +432,10 @@
      * in a background thread. Later, if there is an actual startInput it will wait on
      * main thread till the background thread completes.
      */
-    private CompletableFuture<Void> mWindowFocusGainFuture;
+    private Future<?> mWindowFocusGainFuture;
+
+    private ExecutorService mStartInputWorker = Executors.newSingleThreadExecutor(
+            new ImeThreadFactory("StartInputWorker"));
 
     /**
      * The instance that has previously been sent to the input method.
@@ -790,6 +796,19 @@
         }
     }
 
+    private static class ImeThreadFactory implements ThreadFactory {
+        private final String mThreadName;
+
+        ImeThreadFactory(String name) {
+            mThreadName = name;
+        }
+
+        @Override
+        public Thread newThread(Runnable r) {
+            return new Thread(r, mThreadName);
+        }
+    }
+
     final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
         @Override
         protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
@@ -1978,7 +1997,7 @@
         if (mWindowFocusGainFuture != null) {
             mWindowFocusGainFuture.cancel(false/* mayInterruptIfRunning */);
         }
-        mWindowFocusGainFuture = CompletableFuture.runAsync(() -> {
+        mWindowFocusGainFuture = mStartInputWorker.submit(() -> {
             if (checkFocusNoStartInput(forceNewFocus1)) {
                 // We need to restart input on the current focus view.  This
                 // should be done in conjunction with telling the system service
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index a0cf534..20af76b 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -5921,8 +5921,6 @@
         // The offsets of that last touch down event. Remembered to start selection there.
         private int mMinTouchOffset, mMaxTouchOffset;
 
-        private boolean mGestureStayedInTapRegion;
-
         // Where the user first starts the drag motion.
         private int mStartOffset = -1;
 
@@ -6029,8 +6027,7 @@
                                 eventX, eventY);
 
                         // Double tap detection
-                        if (mGestureStayedInTapRegion
-                                && mTouchState.isMultiTapInSameArea()
+                        if (mTouchState.isMultiTapInSameArea()
                                 && (isMouse || isPositionOnText(eventX, eventY))) {
                             if (TextView.DEBUG_CURSOR) {
                                 logCursor("SelectionModifierCursorController: onTouchEvent",
@@ -6043,7 +6040,6 @@
                             }
                             mDiscardNextActionUp = true;
                         }
-                        mGestureStayedInTapRegion = true;
                         mHaventMovedEnoughToStartDrag = true;
                     }
                     break;
@@ -6059,25 +6055,8 @@
                     break;
 
                 case MotionEvent.ACTION_MOVE:
-                    final ViewConfiguration viewConfig = ViewConfiguration.get(
-                            mTextView.getContext());
-
-                    if (mGestureStayedInTapRegion || mHaventMovedEnoughToStartDrag) {
-                        final float deltaX = eventX - mTouchState.getLastDownX();
-                        final float deltaY = eventY - mTouchState.getLastDownY();
-                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
-
-                        if (mGestureStayedInTapRegion) {
-                            int doubleTapTouchSlop = viewConfig.getScaledDoubleTapTouchSlop();
-                            mGestureStayedInTapRegion =
-                                    distanceSquared <= doubleTapTouchSlop * doubleTapTouchSlop;
-                        }
-                        if (mHaventMovedEnoughToStartDrag) {
-                            // We don't start dragging until the user has moved enough.
-                            int touchSlop = viewConfig.getScaledTouchSlop();
-                            mHaventMovedEnoughToStartDrag =
-                                    distanceSquared <= touchSlop * touchSlop;
-                        }
+                    if (mHaventMovedEnoughToStartDrag) {
+                        mHaventMovedEnoughToStartDrag = !mTouchState.isMovedEnoughForDrag();
                     }
 
                     if (isMouse && !isDragAcceleratorActive()) {
diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java
index d53099d..6277afe 100644
--- a/core/java/android/widget/EditorTouchState.java
+++ b/core/java/android/widget/EditorTouchState.java
@@ -31,13 +31,15 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Helper class used by {@link Editor} to track state for touch events.
+ * Helper class used by {@link Editor} to track state for touch events. Ideally the logic here
+ * should be replaced with {@link android.view.GestureDetector}.
  *
  * @hide
  */
 @VisibleForTesting(visibility = PACKAGE)
 public class EditorTouchState {
     private float mLastDownX, mLastDownY;
+    private long mLastDownMillis;
     private float mLastUpX, mLastUpY;
     private long mLastUpMillis;
 
@@ -106,9 +108,18 @@
         final int action = event.getActionMasked();
         if (action == MotionEvent.ACTION_DOWN) {
             final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
+
+            // We check both the time between the last up and current down event, as well as the
+            // time between the first down and up events. The latter check is necessary to handle
+            // the case when the user taps, drags/holds for some time, and then lifts up and
+            // quickly taps in the same area. This scenario should not be treated as a double-tap.
+            // This follows the behavior in GestureDetector.
             final long millisSinceLastUp = event.getEventTime() - mLastUpMillis;
+            final long millisBetweenLastDownAndLastUp = mLastUpMillis - mLastDownMillis;
+
             // Detect double tap and triple click.
             if (millisSinceLastUp <= ViewConfiguration.getDoubleTapTimeout()
+                    && millisBetweenLastDownAndLastUp <= ViewConfiguration.getDoubleTapTimeout()
                     && (mMultiTapStatus == MultiTapStatus.FIRST_TAP
                     || (mMultiTapStatus == MultiTapStatus.DOUBLE_TAP && isMouse))) {
                 if (mMultiTapStatus == MultiTapStatus.FIRST_TAP) {
@@ -133,6 +144,7 @@
             }
             mLastDownX = event.getX();
             mLastDownY = event.getY();
+            mLastDownMillis = event.getEventTime();
             mMovedEnoughForDrag = false;
             mIsDragCloseToVertical = false;
         } else if (action == MotionEvent.ACTION_UP) {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index d86766e..01a0e9b 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -565,7 +565,8 @@
     }
 
     private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) {
-        if (icon != null && icon.getType() == Icon.TYPE_URI) {
+        if (icon != null && (icon.getType() == Icon.TYPE_URI
+                || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
             visitor.accept(icon.getUri());
         }
     }
diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
index f9ba34e..de204ba 100644
--- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -16,6 +16,7 @@
 package com.android.internal.app;
 
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 import static android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
@@ -24,13 +25,17 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -49,7 +54,10 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
 
 /**
  * Activity used to display and persist a service or feature target for the Accessibility button.
@@ -59,13 +67,46 @@
     private static final String MAGNIFICATION_COMPONENT_ID =
             "com.android.server.accessibility.MagnificationController";
 
+    private static final char SERVICES_SEPARATOR = ':';
+    private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
+            new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
+    private static final int ACCESSIBILITY_BUTTON_USER_TYPE = convertToUserType(
+            ACCESSIBILITY_BUTTON);
+    private static final int ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE = convertToUserType(
+            ACCESSIBILITY_SHORTCUT_KEY);
+
     private int mShortcutType;
     private List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
-    private List<AccessibilityButtonTarget> mReadyToBeDisabledTargets = new ArrayList<>();
     private AlertDialog mAlertDialog;
     private TargetAdapter mTargetAdapter;
 
     /**
+     * Annotation for different user shortcut type UI type.
+     *
+     * {@code DEFAULT} for displaying default value.
+     * {@code SOFTWARE} for displaying specifying the accessibility services or features which
+     * choose accessibility button in the navigation bar as preferred shortcut.
+     * {@code HARDWARE} for displaying specifying the accessibility services or features which
+     * choose accessibility shortcut as preferred shortcut.
+     * {@code TRIPLETAP} for displaying specifying magnification to be toggled via quickly
+     * tapping screen 3 times as preferred shortcut.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            UserShortcutType.DEFAULT,
+            UserShortcutType.SOFTWARE,
+            UserShortcutType.HARDWARE,
+            UserShortcutType.TRIPLETAP,
+    })
+    /** Denotes the user shortcut type. */
+    public @interface UserShortcutType {
+        int DEFAULT = 0;
+        int SOFTWARE = 1; // 1 << 0
+        int HARDWARE = 2; // 1 << 1
+        int TRIPLETAP = 4; // 1 << 2
+    }
+
+    /**
      * Annotation for different accessibilityService fragment UI type.
      *
      * {@code LEGACY} for displaying appearance aligned with sdk version Q accessibility service
@@ -117,7 +158,6 @@
                 ACCESSIBILITY_BUTTON);
         mTargets.addAll(getServiceTargets(this, mShortcutType));
 
-        // TODO(b/146815548): Will add title to separate which one type
         mTargetAdapter = new TargetAdapter(mTargets);
         mAlertDialog = new AlertDialog.Builder(this)
                 .setAdapter(mTargetAdapter, /* listener= */ null)
@@ -269,8 +309,10 @@
 
             switch (target.getFragmentType()) {
                 case AccessibilityServiceFragmentType.LEGACY:
+                    updateLegacyActionItemVisibility(context, holder);
+                    break;
                 case AccessibilityServiceFragmentType.INVISIBLE:
-                    updateLegacyOrInvisibleActionItemVisibility(context, holder);
+                    updateInvisibleActionItemVisibility(context, holder);
                     break;
                 case AccessibilityServiceFragmentType.INTUITIVE:
                     updateIntuitiveActionItemVisibility(context, holder, target);
@@ -283,9 +325,21 @@
             }
         }
 
-        private void updateLegacyOrInvisibleActionItemVisibility(@NonNull Context context,
+        private void updateLegacyActionItemVisibility(@NonNull Context context,
                 @NonNull ViewHolder holder) {
-            final boolean isEditMenuMode = mShortcutMenuMode == ShortcutMenuMode.EDIT;
+            final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
+
+            holder.mLabelView.setEnabled(!isEditMenuMode);
+            holder.mViewItem.setEnabled(!isEditMenuMode);
+            holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
+            holder.mViewItem.setVisibility(View.VISIBLE);
+            holder.mSwitchItem.setVisibility(View.GONE);
+            holder.mItemContainer.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
+        }
+
+        private void updateInvisibleActionItemVisibility(@NonNull Context context,
+                @NonNull ViewHolder holder) {
+            final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
 
             holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
             holder.mViewItem.setVisibility(View.VISIBLE);
@@ -295,7 +349,7 @@
 
         private void updateIntuitiveActionItemVisibility(@NonNull Context context,
                 @NonNull ViewHolder holder, AccessibilityButtonTarget target) {
-            final boolean isEditMenuMode = mShortcutMenuMode == ShortcutMenuMode.EDIT;
+            final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
 
             holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
             holder.mViewItem.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
@@ -306,7 +360,7 @@
 
         private void updateBounceActionItemVisibility(@NonNull Context context,
                 @NonNull ViewHolder holder) {
-            final boolean isEditMenuMode = mShortcutMenuMode == ShortcutMenuMode.EDIT;
+            final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
 
             holder.mViewItem.setImageDrawable(
                     isEditMenuMode ? context.getDrawable(R.drawable.ic_delete_item)
@@ -383,20 +437,67 @@
     }
 
     private void onTargetDeleted(AdapterView<?> parent, View view, int position, long id) {
-        // TODO(b/147027236): Will discuss with UX designer what UX behavior about deleting item
-        //  is good for user.
-        mReadyToBeDisabledTargets.add(mTargets.get(position));
+        final AccessibilityButtonTarget target = mTargets.get(position);
+        final ComponentName targetComponentName =
+                ComponentName.unflattenFromString(target.getId());
+
+        switch (target.getFragmentType()) {
+            case AccessibilityServiceFragmentType.INVISIBLE:
+                onInvisibleTargetDeleted(targetComponentName);
+                break;
+            case AccessibilityServiceFragmentType.INTUITIVE:
+                onIntuitiveTargetDeleted(targetComponentName);
+                break;
+            case AccessibilityServiceFragmentType.LEGACY:
+            case AccessibilityServiceFragmentType.BOUNCE:
+                // Do nothing
+                break;
+            default:
+                throw new IllegalStateException("Unexpected fragment type");
+        }
+
         mTargets.remove(position);
         mTargetAdapter.notifyDataSetChanged();
+
+        if (mTargets.isEmpty()) {
+            mAlertDialog.dismiss();
+        }
+    }
+
+    private void onInvisibleTargetDeleted(ComponentName componentName) {
+        if (mShortcutType == ACCESSIBILITY_BUTTON) {
+            optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName);
+
+            if (!hasValueInSettings(this,
+                    ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName)) {
+                setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+            }
+        } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+            optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName);
+
+            if (!hasValueInSettings(this,
+                    ACCESSIBILITY_BUTTON_USER_TYPE, componentName)) {
+                setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+            }
+        } else {
+            throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
+        }
+    }
+
+    private void onIntuitiveTargetDeleted(ComponentName componentName) {
+        if (mShortcutType == ACCESSIBILITY_BUTTON) {
+            optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName);
+        } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+            optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName);
+        } else {
+            throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
+        }
     }
 
     private void onCancelButtonClicked() {
-        resetAndUpdateTargets();
-
         mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH);
         mTargetAdapter.notifyDataSetChanged();
 
-        mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
         mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
                 getString(R.string.edit_accessibility_shortcut_menu_button));
 
@@ -407,49 +508,181 @@
         mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.EDIT);
         mTargetAdapter.notifyDataSetChanged();
 
-        mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setText(
+        mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
                 getString(R.string.cancel_accessibility_shortcut_menu_button));
-        mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.VISIBLE);
-        mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
-                getString(R.string.save_accessibility_shortcut_menu_button));
-
-        updateDialogListeners();
-    }
-
-    private void onSaveButtonClicked() {
-        disableTargets();
-        resetAndUpdateTargets();
-
-        mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH);
-        mTargetAdapter.notifyDataSetChanged();
-
-        mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
-        mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
-                getString(R.string.edit_accessibility_shortcut_menu_button));
 
         updateDialogListeners();
     }
 
     private void updateDialogListeners() {
         final boolean isEditMenuMode =
-                mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT;
+                (mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT);
 
-        mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(
-                view -> onCancelButtonClicked());
         mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(
-                isEditMenuMode ? view -> onSaveButtonClicked() : view -> onEditButtonClicked());
+                isEditMenuMode ? view -> onCancelButtonClicked() : view -> onEditButtonClicked());
         mAlertDialog.getListView().setOnItemClickListener(
                 isEditMenuMode ? this::onTargetDeleted : this::onTargetSelected);
     }
 
-    private void disableTargets() {
-        for (AccessibilityButtonTarget service : mReadyToBeDisabledTargets) {
-            // TODO(b/146967898): disable services.
+    /**
+     * @return the set of enabled accessibility services for {@param userId}. If there are no
+     * services, it returns the unmodifiable {@link Collections#emptySet()}.
+     */
+    private Set<ComponentName> getEnabledServicesFromSettings(Context context, int userId) {
+        final String enabledServicesSetting = Settings.Secure.getStringForUser(
+                context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                userId);
+        if (TextUtils.isEmpty(enabledServicesSetting)) {
+            return Collections.emptySet();
+        }
+
+        final Set<ComponentName> enabledServices = new HashSet<>();
+        final TextUtils.StringSplitter colonSplitter =
+                new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
+        colonSplitter.setString(enabledServicesSetting);
+
+        for (String componentNameString : colonSplitter) {
+            final ComponentName enabledService = ComponentName.unflattenFromString(
+                    componentNameString);
+            if (enabledService != null) {
+                enabledServices.add(enabledService);
+            }
+        }
+
+        return enabledServices;
+    }
+
+    /**
+     * Changes an accessibility component's state.
+     */
+    private void setAccessibilityServiceState(Context context, ComponentName componentName,
+            boolean enabled) {
+        setAccessibilityServiceState(context, componentName, enabled, UserHandle.myUserId());
+    }
+
+    /**
+     * Changes an accessibility component's state for {@param userId}.
+     */
+    private void setAccessibilityServiceState(Context context, ComponentName componentName,
+            boolean enabled, int userId) {
+        Set<ComponentName> enabledServices = getEnabledServicesFromSettings(
+                context, userId);
+
+        if (enabledServices.isEmpty()) {
+            enabledServices = new ArraySet<>(/* capacity= */ 1);
+        }
+
+        if (enabled) {
+            enabledServices.add(componentName);
+        } else {
+            enabledServices.remove(componentName);
+        }
+
+        final StringBuilder enabledServicesBuilder = new StringBuilder();
+        for (ComponentName enabledService : enabledServices) {
+            enabledServicesBuilder.append(enabledService.flattenToString());
+            enabledServicesBuilder.append(
+                    SERVICES_SEPARATOR);
+        }
+
+        final int enabledServicesBuilderLength = enabledServicesBuilder.length();
+        if (enabledServicesBuilderLength > 0) {
+            enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
+        }
+
+        Settings.Secure.putStringForUser(context.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                enabledServicesBuilder.toString(), userId);
+    }
+
+    /**
+     * Opts out component name into colon-separated {@code shortcutType} key's string in Settings.
+     *
+     * @param context The current context.
+     * @param shortcutType The preferred shortcut type user selected.
+     * @param componentName The component name that need to be opted out from Settings.
+     */
+    private void optOutValueFromSettings(
+            Context context, int shortcutType, ComponentName componentName) {
+        final StringJoiner joiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
+        final String targetsKey = convertToKey(shortcutType);
+        final String targetsValue = Settings.Secure.getString(context.getContentResolver(),
+                targetsKey);
+
+        if (TextUtils.isEmpty(targetsValue)) {
+            return;
+        }
+
+        sStringColonSplitter.setString(targetsValue);
+        while (sStringColonSplitter.hasNext()) {
+            final String name = sStringColonSplitter.next();
+            if (TextUtils.isEmpty(name) || (componentName.flattenToString()).equals(name)) {
+                continue;
+            }
+            joiner.add(name);
+        }
+
+        Settings.Secure.putString(context.getContentResolver(), targetsKey, joiner.toString());
+    }
+
+    /**
+     * Returns if component name existed in Settings.
+     *
+     * @param context The current context.
+     * @param shortcutType The preferred shortcut type user selected.
+     * @param componentName The component name that need to be checked existed in Settings.
+     * @return {@code true} if componentName existed in Settings.
+     */
+    private boolean hasValueInSettings(Context context, @UserShortcutType int shortcutType,
+            @NonNull ComponentName componentName) {
+        final String targetKey = convertToKey(shortcutType);
+        final String targetString = Settings.Secure.getString(context.getContentResolver(),
+                targetKey);
+
+        if (TextUtils.isEmpty(targetString)) {
+            return false;
+        }
+
+        sStringColonSplitter.setString(targetString);
+        while (sStringColonSplitter.hasNext()) {
+            final String name = sStringColonSplitter.next();
+            if ((componentName.flattenToString()).equals(name)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Converts {@link UserShortcutType} to key in Settings.
+     *
+     * @param type The shortcut type.
+     * @return Mapping key in Settings.
+     */
+    private String convertToKey(@UserShortcutType int type) {
+        switch (type) {
+            case UserShortcutType.SOFTWARE:
+                return Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT;
+            case UserShortcutType.HARDWARE:
+                return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
+            case UserShortcutType.TRIPLETAP:
+                return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
+            default:
+                throw new IllegalArgumentException(
+                        "Unsupported user shortcut type: " + type);
         }
     }
 
-    private void resetAndUpdateTargets() {
-        mTargets.clear();
-        mTargets.addAll(getServiceTargets(this, mShortcutType));
+    private static @UserShortcutType int convertToUserType(@ShortcutType int type) {
+        switch (type) {
+            case ACCESSIBILITY_BUTTON:
+                return UserShortcutType.SOFTWARE;
+            case ACCESSIBILITY_SHORTCUT_KEY:
+                return UserShortcutType.HARDWARE;
+            default:
+                throw new IllegalArgumentException(
+                        "Unsupported shortcut type:" + type);
+        }
     }
 }
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index d94294f..74bfb96 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -61,10 +61,6 @@
     int setParameter(in ParcelUuid soundModelId, in ModelParams modelParam,
         int value);
 
-    /**
-     * @throws UnsupportedOperationException if hal or model do not support this API.
-     * @throws IllegalArgumentException if invalid model handle or parameter is passed.
-     */
     int getParameter(in ParcelUuid soundModelId, in ModelParams modelParam);
 
     @nullable SoundTrigger.ModelParamRange queryParameter(in ParcelUuid soundModelId,
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index f462f5d..be2d1d6 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -26,6 +26,7 @@
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.IVoiceInteractionSessionListener;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.service.voice.IVoiceInteractionService;
 import android.service.voice.IVoiceInteractionSession;
@@ -91,6 +92,49 @@
      */
     int stopRecognition(in IVoiceInteractionService service, int keyphraseId,
             in IRecognitionStatusCallback callback);
+    /**
+     * Set a model specific ModelParams with the given value. This
+     * parameter will keep its value for the duration the model is loaded regardless of starting and
+     * stopping recognition. Once the model is unloaded, the value will be lost.
+     * queryParameter should be checked first before calling this method.
+     *
+     * @param service The current VoiceInteractionService.
+     * @param keyphraseId The unique identifier for the keyphrase.
+     * @param modelParam   ModelParams
+     * @param value        Value to set
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+     *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+     *           if API is not supported by HAL
+     */
+    int setParameter(in IVoiceInteractionService service, int keyphraseId,
+            in ModelParams modelParam, int value);
+    /**
+     * Get a model specific ModelParams. This parameter will keep its value
+     * for the duration the model is loaded regardless of starting and stopping recognition.
+     * Once the model is unloaded, the value will be lost. If the value is not set, a default
+     * value is returned. See ModelParams for parameter default values.
+     * queryParameter should be checked first before calling this method.
+     *
+     * @param service The current VoiceInteractionService.
+     * @param keyphraseId The unique identifier for the keyphrase.
+     * @param modelParam   ModelParams
+     * @return value of parameter
+     */
+    int getParameter(in IVoiceInteractionService service, int keyphraseId,
+            in ModelParams modelParam);
+    /**
+     * Determine if parameter control is supported for the given model handle.
+     * This method should be checked prior to calling setParameter or getParameter.
+     *
+     * @param service The current VoiceInteractionService.
+     * @param keyphraseId The unique identifier for the keyphrase.
+     * @param modelParam ModelParams
+     * @return supported range of parameter, null if not supported
+     */
+    @nullable SoundTrigger.ModelParamRange queryParameter(in IVoiceInteractionService service,
+            int keyphraseId, in ModelParams modelParam);
 
     /**
      * @return the component name for the currently active voice interaction service
diff --git a/core/java/com/android/internal/compat/AndroidBuildClassifier.java b/core/java/com/android/internal/compat/AndroidBuildClassifier.java
new file mode 100644
index 0000000..0b937fa
--- /dev/null
+++ b/core/java/com/android/internal/compat/AndroidBuildClassifier.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.internal.compat;
+
+import android.os.Build;
+
+/**
+ * Platform private class for determining the type of Android build installed.
+ *
+ */
+public class AndroidBuildClassifier {
+
+    public boolean isDebuggableBuild() {
+        return Build.IS_DEBUGGABLE;
+    }
+
+    public boolean isFinalBuild() {
+        return "REL".equals(Build.VERSION.CODENAME);
+    }
+}
diff --git a/core/java/com/android/internal/compat/IOverrideValidator.aidl b/core/java/com/android/internal/compat/IOverrideValidator.aidl
new file mode 100644
index 0000000..add4708
--- /dev/null
+++ b/core/java/com/android/internal/compat/IOverrideValidator.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.compat;
+
+import android.content.pm.ApplicationInfo;
+
+import com.android.internal.compat.OverrideAllowedState;
+
+/**
+ * Platform private API for determining whether a changeId can be overridden.
+ *
+ * {@hide}
+ */
+interface IOverrideValidator
+{
+    /**
+     * Validation function.
+     * @param changeId    id of the change to be toggled on or off.
+     * @param packageName package of the app for which the change should be overridden.
+     * @return {@link OverrideAllowedState} specifying whether the change can be overridden for
+     * the given package or a reason why not.
+     */
+    OverrideAllowedState getOverrideAllowedState(long changeId, String packageName);
+}
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index 7dcb12c..4c203d3 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.compat;
 
 import android.content.pm.ApplicationInfo;
+import com.android.internal.compat.IOverrideValidator;
 import java.util.Map;
 
 parcelable CompatibilityChangeConfig;
@@ -195,4 +196,9 @@
      * @return An array of {@link CompatChangeInfo} known to the service.
      */
     CompatibilityChangeInfo[] listAllChanges();
+
+    /**
+     * Get an instance that can determine whether a changeid can be overridden for a package name.
+     */
+    IOverrideValidator getOverrideValidator();
 }
diff --git a/core/java/android/net/NetworkMisc.aidl b/core/java/com/android/internal/compat/OverrideAllowedState.aidl
similarity index 81%
copy from core/java/android/net/NetworkMisc.aidl
copy to core/java/com/android/internal/compat/OverrideAllowedState.aidl
index c65583f..10ceac7 100644
--- a/core/java/android/net/NetworkMisc.aidl
+++ b/core/java/com/android/internal/compat/OverrideAllowedState.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.net;
+package com.android.internal.compat;
 
-parcelable NetworkMisc;
+parcelable OverrideAllowedState;
\ No newline at end of file
diff --git a/core/java/com/android/internal/compat/OverrideAllowedState.java b/core/java/com/android/internal/compat/OverrideAllowedState.java
new file mode 100644
index 0000000..56216c2
--- /dev/null
+++ b/core/java/com/android/internal/compat/OverrideAllowedState.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.compat;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class contains all the possible override allowed states.
+ */
+public final class OverrideAllowedState implements Parcelable {
+    @IntDef({
+            ALLOWED,
+            DISABLED_NOT_DEBUGGABLE,
+            DISABLED_NON_TARGET_SDK,
+            DISABLED_TARGET_SDK_TOO_HIGH,
+            PACKAGE_DOES_NOT_EXIST
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface State {
+    }
+
+    /**
+     * Change can be overridden.
+     */
+    public static final int ALLOWED = 0;
+    /**
+     * Change cannot be overridden, due to the app not being debuggable.
+     */
+    public static final int DISABLED_NOT_DEBUGGABLE = 1;
+    /**
+     * Change cannot be overridden, due to the build being non-debuggable and the change being
+     * non-targetSdk.
+     */
+    public static final int DISABLED_NON_TARGET_SDK = 2;
+    /**
+     * Change cannot be overridden, due to the app's targetSdk being above the change's targetSdk.
+     */
+    public static final int DISABLED_TARGET_SDK_TOO_HIGH = 3;
+    /**
+     * Package does not exist.
+     */
+    public static final int PACKAGE_DOES_NOT_EXIST = 4;
+
+    @State
+    public final int state;
+    public final int appTargetSdk;
+    public final int changeIdTargetSdk;
+
+    private OverrideAllowedState(Parcel parcel) {
+        state = parcel.readInt();
+        appTargetSdk = parcel.readInt();
+        changeIdTargetSdk = parcel.readInt();
+    }
+
+    public OverrideAllowedState(@State int state, int appTargetSdk, int changeIdTargetSdk) {
+        this.state = state;
+        this.appTargetSdk = appTargetSdk;
+        this.changeIdTargetSdk = changeIdTargetSdk;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(state);
+        out.writeInt(appTargetSdk);
+        out.writeInt(changeIdTargetSdk);
+    }
+
+    /**
+     * Enforces the policy for overriding compat changes.
+     *
+     * @param changeId    the change id that was attempted to be overridden.
+     * @param packageName the package for which the attempt was made.
+     * @throws SecurityException if the policy forbids this operation.
+     */
+    public void enforce(long changeId, String packageName)
+            throws SecurityException {
+        switch (state) {
+            case ALLOWED:
+                return;
+            case DISABLED_NOT_DEBUGGABLE:
+                throw new SecurityException(
+                        "Cannot override a change on a non-debuggable app and user build.");
+            case DISABLED_NON_TARGET_SDK:
+                throw new SecurityException(
+                        "Cannot override a default enabled/disabled change on a user build.");
+            case DISABLED_TARGET_SDK_TOO_HIGH:
+                throw new SecurityException(String.format(
+                        "Cannot override %1$d for %2$s because the app's targetSdk (%3$d) is "
+                                + "above the change's targetSdk threshold (%4$d)",
+                        changeId, packageName, appTargetSdk, changeIdTargetSdk));
+            case PACKAGE_DOES_NOT_EXIST:
+                throw new SecurityException(String.format(
+                        "Cannot override %1$d for %2$s because the package does not exist, and "
+                                + "the change is targetSdk gated.",
+                        changeId, packageName));
+        }
+    }
+
+    public static final @NonNull
+            Parcelable.Creator<OverrideAllowedState> CREATOR =
+                new Parcelable.Creator<OverrideAllowedState>() {
+                public OverrideAllowedState createFromParcel(Parcel parcel) {
+                    OverrideAllowedState info = new OverrideAllowedState(parcel);
+                    return info;
+                }
+
+                public OverrideAllowedState[] newArray(int size) {
+                    return new OverrideAllowedState[size];
+                }
+            };
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof OverrideAllowedState)) {
+            return false;
+        }
+        OverrideAllowedState otherState = (OverrideAllowedState) obj;
+        return state == otherState.state
+                && appTargetSdk == otherState.appTargetSdk
+                && changeIdTargetSdk == otherState.changeIdTargetSdk;
+    }
+}
diff --git a/core/java/com/android/internal/infra/AndroidFuture.java b/core/java/com/android/internal/infra/AndroidFuture.java
index b250578..9f15d89 100644
--- a/core/java/com/android/internal/infra/AndroidFuture.java
+++ b/core/java/com/android/internal/infra/AndroidFuture.java
@@ -75,6 +75,7 @@
 
     private static final boolean DEBUG = false;
     private static final String LOG_TAG = AndroidFuture.class.getSimpleName();
+    private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
 
     private final @NonNull Object mLock = new Object();
     @GuardedBy("mLock")
@@ -95,15 +96,7 @@
             // Done
             if (in.readBoolean()) {
                 // Failed
-                try {
-                    in.readException();
-                } catch (Throwable e) {
-                    completeExceptionally(e);
-                }
-                if (!isCompletedExceptionally()) {
-                    throw new IllegalStateException(
-                            "Error unparceling AndroidFuture: exception expected");
-                }
+                completeExceptionally(unparcelException(in));
             } else {
                 // Success
                 complete((T) in.readValue(null));
@@ -512,14 +505,9 @@
             T result;
             try {
                 result = get();
-            } catch (Exception t) {
-                // Exceptions coming out of get() are wrapped in ExecutionException, which is not
-                // handled by Parcel.
-                if (t instanceof ExecutionException && t.getCause() instanceof Exception) {
-                    t = (Exception) t.getCause();
-                }
+            } catch (Throwable t) {
                 dest.writeBoolean(true);
-                dest.writeException(t);
+                parcelException(dest, unwrapExecutionException(t));
                 return;
             }
             dest.writeBoolean(false);
@@ -528,22 +516,76 @@
             dest.writeStrongBinder(new IAndroidFuture.Stub() {
                 @Override
                 public void complete(AndroidFuture resultContainer) {
+                    boolean changed;
                     try {
-                        AndroidFuture.this.complete((T) resultContainer.get());
+                        changed = AndroidFuture.this.complete((T) resultContainer.get());
                     } catch (Throwable t) {
-                        // If resultContainer was completed exceptionally, get() wraps the
-                        // underlying exception in an ExecutionException. Unwrap it now to avoid
-                        // double-layering ExecutionExceptions.
-                        if (t instanceof ExecutionException && t.getCause() != null) {
-                            t = t.getCause();
-                        }
-                        completeExceptionally(t);
+                        changed = completeExceptionally(unwrapExecutionException(t));
+                    }
+                    if (!changed) {
+                        Log.w(LOG_TAG, "Remote result " + resultContainer
+                                + " ignored, as local future is already completed: "
+                                + AndroidFuture.this);
                     }
                 }
             }.asBinder());
         }
     }
 
+    /**
+     * Exceptions coming out of {@link #get} are wrapped in {@link ExecutionException}
+     */
+    Throwable unwrapExecutionException(Throwable t) {
+        return t instanceof ExecutionException
+                ? t.getCause()
+                : t;
+    }
+
+    /**
+     * Alternative to {@link Parcel#writeException} that stores the stack trace, in a
+     * way consistent with the binder IPC exception propagation behavior.
+     */
+    private static void parcelException(Parcel p, @Nullable Throwable t) {
+        p.writeBoolean(t == null);
+        if (t == null) {
+            return;
+        }
+
+        p.writeInt(Parcel.getExceptionCode(t));
+        p.writeString(t.getClass().getName());
+        p.writeString(t.getMessage());
+        p.writeStackTrace(t);
+        parcelException(p, t.getCause());
+    }
+
+    /**
+     * @see #parcelException
+     */
+    private static @Nullable Throwable unparcelException(Parcel p) {
+        if (p.readBoolean()) {
+            return null;
+        }
+
+        int exCode = p.readInt();
+        String cls = p.readString();
+        String msg = p.readString();
+        String stackTrace = p.readInt() > 0 ? p.readString() : "\t<stack trace unavailable>";
+        msg += "\n" + stackTrace;
+
+        Exception ex = p.createExceptionOrNull(exCode, msg);
+        if (ex == null) {
+            ex = new RuntimeException(cls + ": " + msg);
+        }
+        ex.setStackTrace(EMPTY_STACK_TRACE);
+
+        Throwable cause = unparcelException(p);
+        if (cause != null) {
+            ex.initCause(ex);
+        }
+
+        return ex;
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java
index cca97f6..3a450de 100644
--- a/core/java/com/android/internal/logging/UiEventLogger.java
+++ b/core/java/com/android/internal/logging/UiEventLogger.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.logging;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 /**
  * Logging interface for UI events. Normal implementation is UiEventLoggerImpl.
  * For testing, use fake implementation UiEventLoggerFake.
@@ -26,13 +29,24 @@
     /** Put your Event IDs in enums that implement this interface, and document them using the
      * UiEventEnum annotation.
      * Event IDs must be globally unique. This will be enforced by tooling (forthcoming).
-     * OEMs should use event IDs above 100000.
+     * OEMs should use event IDs above 100000 and below 1000000 (1 million).
      */
     interface UiEventEnum {
         int getId();
     }
+
     /**
-     * Log a simple event, with no package or instance ID.
+     * Log a simple event, with no package information. Does nothing if event.getId() <= 0.
+     * @param event an enum implementing UiEventEnum interface.
      */
-    void log(UiEventEnum eventID);
+    void log(@NonNull UiEventEnum event);
+
+    /**
+     * Log an event with package information. Does nothing if event.getId() <= 0.
+     * Give both uid and packageName if both are known, but one may be omitted if unknown.
+     * @param event an enum implementing UiEventEnum interface.
+     * @param uid the uid of the relevant app, if known (0 otherwise).
+     * @param packageName the package name of the relevant app, if known (null otherwise).
+     */
+    void log(@NonNull UiEventEnum event, int uid, @Nullable String packageName);
 }
diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
index e64fba2..bdf460c 100644
--- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java
+++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
@@ -24,14 +24,16 @@
  * See UiEventReported atom in atoms.proto for more context.
  */
 public class UiEventLoggerImpl implements UiEventLogger {
-    /**
-     * Log a simple event, with no package or instance ID.
-     */
     @Override
     public void log(UiEventEnum event) {
+        log(event, 0, null);
+    }
+
+    @Override
+    public void log(UiEventEnum event, int uid, String packageName) {
         final int eventID = event.getId();
         if (eventID > 0) {
-            StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, 0, null);
+            StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, uid, packageName);
         }
     }
 }
diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
index 92e9bbb..6be5b81 100644
--- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
+++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
@@ -30,7 +30,7 @@
     /**
      * Immutable data class used to record fake log events.
      */
-    public class FakeUiEvent {
+    public static class FakeUiEvent {
         public final int eventId;
         public final int uid;
         public final String packageName;
@@ -44,15 +44,20 @@
 
     private Queue<FakeUiEvent> mLogs = new LinkedList<FakeUiEvent>();
 
-    @Override
-    public void log(UiEventEnum event) {
-        final int eventId = event.getId();
-        if (eventId > 0) {
-            mLogs.offer(new FakeUiEvent(eventId, 0, null));
-        }
-    }
-
     public Queue<FakeUiEvent> getLogs() {
         return mLogs;
     }
+
+    @Override
+    public void log(UiEventEnum event) {
+        log(event, 0, null);
+    }
+
+    @Override
+    public void log(UiEventEnum event, int uid, String packageName) {
+        final int eventId = event.getId();
+        if (eventId > 0) {
+            mLogs.offer(new FakeUiEvent(eventId, uid, packageName));
+        }
+    }
 }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 2248b88..f0a346a 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -145,6 +145,11 @@
     /** The lower file system should be bind mounted directly on external storage */
     public static final int MOUNT_EXTERNAL_PASS_THROUGH = IVold.REMOUNT_MODE_PASS_THROUGH;
 
+    /** Use the regular scoped storage filesystem, but Android/ should be writable.
+     * Used to support the applications hosting DownloadManager and the MTP server.
+     */
+    public static final int MOUNT_EXTERNAL_ANDROID_WRITABLE = IVold.REMOUNT_MODE_ANDROID_WRITABLE;
+
     /** Number of bytes sent to the Zygote over USAP pipes or the pool event FD */
     static final int USAP_MANAGEMENT_MESSAGE_BYTES = 8;
 
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index d349954..37f570b 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -376,6 +376,8 @@
                 mMountExternal = Zygote.MOUNT_EXTERNAL_LEGACY;
             } else if (arg.equals("--mount-external-pass-through")) {
                 mMountExternal = Zygote.MOUNT_EXTERNAL_PASS_THROUGH;
+            } else if (arg.equals("--mount-external-android-writable")) {
+                mMountExternal = Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
             } else if (arg.equals("--query-abi-list")) {
                 mAbiListQuery = true;
             } else if (arg.equals("--get-pid")) {
diff --git a/telephony/java/com/android/internal/telephony/IWapPushManager.aidl b/core/java/com/android/internal/telephony/IWapPushManager.aidl
similarity index 82%
rename from telephony/java/com/android/internal/telephony/IWapPushManager.aidl
rename to core/java/com/android/internal/telephony/IWapPushManager.aidl
index 1c3df65..9f6851b 100644
--- a/telephony/java/com/android/internal/telephony/IWapPushManager.aidl
+++ b/core/java/com/android/internal/telephony/IWapPushManager.aidl
@@ -18,6 +18,7 @@
 
 import android.content.Intent;
 
+/** @hide */
 interface IWapPushManager {
     /**
      * Processes WAP push message and triggers the receiver application registered
@@ -26,11 +27,10 @@
     int processMessage(String app_id, String content_type, in Intent intent);
 
     /**
-     * Add receiver application into the application ID table.
-     * Returns true if inserting the information is successfull. Inserting the duplicated
+     * Adds receiver application into the application ID table.
+     * Returns true if inserting the information is successful. Inserting duplicated
      * record in the application ID table is not allowed. Use update/delete method.
      */
-    @UnsupportedAppUsage
     boolean addPackage(String x_app_id, String content_type,
             String package_name, String class_name,
             int app_type, boolean need_signature, boolean further_processing);
@@ -39,17 +39,14 @@
      * Updates receiver application that is last added.
      * Returns true if updating the information is successfull.
      */
-    @UnsupportedAppUsage
     boolean updatePackage(String x_app_id, String content_type,
             String package_name, String class_name,
             int app_type, boolean need_signature, boolean further_processing);
 
     /**
-     * Delites receiver application information.
+     * Deletes receiver application information.
      * Returns true if deleting is successfull.
      */
-    @UnsupportedAppUsage
     boolean deletePackage(String x_app_id, String content_type,
-                            String package_name, String class_name);
+            String package_name, String class_name);
 }
-
diff --git a/core/java/com/android/internal/telephony/WapPushManagerParams.java b/core/java/com/android/internal/telephony/WapPushManagerParams.java
new file mode 100644
index 0000000..eafb8f1
--- /dev/null
+++ b/core/java/com/android/internal/telephony/WapPushManagerParams.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.telephony.WapPushManagerConnector;
+
+/**
+ * WapPushManager constant value definitions.
+ * @hide
+ */
+public class WapPushManagerParams {
+    /**
+     * Application type activity
+     */
+    public static final int APP_TYPE_ACTIVITY = 0;
+
+    /**
+     * Application type service
+     */
+    public static final int APP_TYPE_SERVICE = 1;
+
+    /**
+     * Process Message return value
+     * Message is handled
+     */
+    public static final int MESSAGE_HANDLED = WapPushManagerConnector.RESULT_MESSAGE_HANDLED;
+
+    /**
+     * Process Message return value
+     * Application ID or content type was not found in the application ID table
+     */
+    public static final int APP_QUERY_FAILED = WapPushManagerConnector.RESULT_APP_QUERY_FAILED;
+
+    /**
+     * Process Message return value
+     * Receiver application signature check failed
+     */
+    public static final int SIGNATURE_NO_MATCH = WapPushManagerConnector.RESULT_SIGNATURE_NO_MATCH;
+
+    /**
+     * Process Message return value
+     * Receiver application was not found
+     */
+    public static final int INVALID_RECEIVER_NAME =
+            WapPushManagerConnector.RESULT_INVALID_RECEIVER_NAME;
+
+    /**
+     * Process Message return value
+     * Unknown exception
+     */
+    public static final int EXCEPTION_CAUGHT = WapPushManagerConnector.RESULT_EXCEPTION_CAUGHT;
+
+    /**
+     * Process Message return value
+     * Need further processing after WapPushManager message processing
+     */
+    public static final int FURTHER_PROCESSING = WapPushManagerConnector.RESULT_FURTHER_PROCESSING;
+}
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index 5bc96d8..408a7a8 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -126,7 +126,9 @@
      * @param reference an object reference
      * @return the non-null reference that was validated
      * @throws NullPointerException if {@code reference} is null
+     * @deprecated - use {@link java.util.Objects.requireNonNull} instead.
      */
+    @Deprecated
     @UnsupportedAppUsage
     public static @NonNull <T> T checkNotNull(final T reference) {
         if (reference == null) {
@@ -144,7 +146,9 @@
      *     be converted to a string using {@link String#valueOf(Object)}
      * @return the non-null reference that was validated
      * @throws NullPointerException if {@code reference} is null
+     * @deprecated - use {@link java.util.Objects.requireNonNull} instead.
      */
+    @Deprecated
     @UnsupportedAppUsage
     public static @NonNull <T> T checkNotNull(final T reference, final Object errorMessage) {
         if (reference == null) {
@@ -154,26 +158,6 @@
     }
 
     /**
-     * Ensures that an object reference passed as a parameter to the calling
-     * method is not null.
-     *
-     * @param reference an object reference
-     * @param messageTemplate a printf-style message template to use if the check fails; will
-     *     be converted to a string using {@link String#format(String, Object...)}
-     * @param messageArgs arguments for {@code messageTemplate}
-     * @return the non-null reference that was validated
-     * @throws NullPointerException if {@code reference} is null
-     */
-    public static @NonNull <T> T checkNotNull(final T reference,
-            final String messageTemplate,
-            final Object... messageArgs) {
-        if (reference == null) {
-            throw new NullPointerException(String.format(messageTemplate, messageArgs));
-        }
-        return reference;
-    }
-
-    /**
      * Ensures the truth of an expression involving the state of the calling
      * instance, but not involving any parameters to the calling method.
      *
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
index 43a227a..d7a01c4 100644
--- a/core/java/com/android/internal/widget/RecyclerView.java
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -2011,13 +2011,27 @@
         }
 
         if (!dispatchNestedPreFling(velocityX, velocityY)) {
-            final boolean canScroll = canScrollHorizontal || canScrollVertical;
-            dispatchNestedFling(velocityX, velocityY, canScroll);
+            final View firstChild = mLayout.getChildAt(0);
+            final View lastChild = mLayout.getChildAt(mLayout.getChildCount() - 1);
+            boolean consumed = false;
+            if (velocityY < 0) {
+                consumed = getChildAdapterPosition(firstChild) > 0
+                        || firstChild.getTop() < getPaddingTop();
+            }
+
+            if (velocityY > 0) {
+                consumed = getChildAdapterPosition(lastChild) < mAdapter.getItemCount() - 1
+                        || lastChild.getBottom() > getHeight() - getPaddingBottom();
+            }
+
+            dispatchNestedFling(velocityX, velocityY, consumed);
 
             if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) {
                 return true;
             }
 
+            final boolean canScroll = canScrollHorizontal || canScrollVertical;
+
             if (canScroll) {
                 velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
                 velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 8a59c99..74b481c 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -218,6 +218,7 @@
     final ArrayMap<String, ArraySet<String>> mAllowedAssociations = new ArrayMap<>();
 
     private final ArraySet<String> mBugreportWhitelistedPackages = new ArraySet<>();
+    private final ArraySet<String> mAppDataIsolationWhitelistedApps = new ArraySet<>();
 
     // Map of packagesNames to userTypes. Stored temporarily until cleared by UserManagerService().
     private ArrayMap<String, Set<String>> mPackageToUserTypeWhitelist = new ArrayMap<>();
@@ -389,6 +390,10 @@
         return mRollbackWhitelistedPackages;
     }
 
+    public ArraySet<String> getAppDataIsolationWhitelistedApps() {
+        return mAppDataIsolationWhitelistedApps;
+    }
+
     /**
      * Gets map of packagesNames to userTypes, dictating on which user types each package should be
      * initially installed, and then removes this map from SystemConfig.
@@ -1045,6 +1050,16 @@
                         }
                         XmlUtils.skipCurrentTag(parser);
                     } break;
+                    case "app-data-isolation-whitelisted-app": {
+                        String pkgname = parser.getAttributeValue(null, "package");
+                        if (pkgname == null) {
+                            Slog.w(TAG, "<" + name + "> without package in " + permFile
+                                    + " at " + parser.getPositionDescription());
+                        } else {
+                            mAppDataIsolationWhitelistedApps.add(pkgname);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
                     case "bugreport-whitelisted": {
                         String pkgname = parser.getAttributeValue(null, "package");
                         if (pkgname == null) {
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index fa64fd1..adedffd 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -52,42 +52,34 @@
 
 using namespace android;
 
-jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format) {
-    const char* mimeType;
+const char* getMimeType(SkEncodedImageFormat format) {
     switch (format) {
         case SkEncodedImageFormat::kBMP:
-            mimeType = "image/bmp";
-            break;
+            return "image/bmp";
         case SkEncodedImageFormat::kGIF:
-            mimeType = "image/gif";
-            break;
+            return "image/gif";
         case SkEncodedImageFormat::kICO:
-            mimeType = "image/x-ico";
-            break;
+            return "image/x-ico";
         case SkEncodedImageFormat::kJPEG:
-            mimeType = "image/jpeg";
-            break;
+            return "image/jpeg";
         case SkEncodedImageFormat::kPNG:
-            mimeType = "image/png";
-            break;
+            return "image/png";
         case SkEncodedImageFormat::kWEBP:
-            mimeType = "image/webp";
-            break;
+            return "image/webp";
         case SkEncodedImageFormat::kHEIF:
-            mimeType = "image/heif";
-            break;
+            return "image/heif";
         case SkEncodedImageFormat::kWBMP:
-            mimeType = "image/vnd.wap.wbmp";
-            break;
+            return "image/vnd.wap.wbmp";
         case SkEncodedImageFormat::kDNG:
-            mimeType = "image/x-adobe-dng";
-            break;
+            return "image/x-adobe-dng";
         default:
-            mimeType = nullptr;
-            break;
+            return nullptr;
     }
+}
 
+jstring getMimeTypeAsJavaString(JNIEnv* env, SkEncodedImageFormat format) {
     jstring jstr = nullptr;
+    const char* mimeType = getMimeType(format);
     if (mimeType) {
         // NOTE: Caller should env->ExceptionCheck() for OOM
         // (can't check for nullptr as it's a valid return value)
@@ -289,10 +281,9 @@
 
     // Set the options and return if the client only wants the size.
     if (options != NULL) {
-        jstring mimeType = encodedFormatToString(
-                env, (SkEncodedImageFormat)codec->getEncodedFormat());
+        jstring mimeType = getMimeTypeAsJavaString(env, codec->getEncodedFormat());
         if (env->ExceptionCheck()) {
-            return nullObjectReturn("OOM in encodedFormatToString()");
+            return nullObjectReturn("OOM in getMimeTypeAsJavaString()");
         }
         env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
         env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h
index e37c98d..45bffc4 100644
--- a/core/jni/android/graphics/BitmapFactory.h
+++ b/core/jni/android/graphics/BitmapFactory.h
@@ -26,6 +26,6 @@
 extern jclass gBitmapConfig_class;
 extern jmethodID gBitmapConfig_nativeToConfigMethodID;
 
-jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format);
+jstring getMimeTypeAsJavaString(JNIEnv*, SkEncodedImageFormat);
 
 #endif  // _ANDROID_GRAPHICS_BITMAP_FACTORY_H_
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index f18632d..06b4ff8 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -197,7 +197,7 @@
         env->SetIntField(options, gOptions_heightFieldID, bitmap.height());
 
         env->SetObjectField(options, gOptions_mimeFieldID,
-                encodedFormatToString(env, (SkEncodedImageFormat)brd->getEncodedFormat()));
+                getMimeTypeAsJavaString(env, brd->getEncodedFormat()));
         if (env->ExceptionCheck()) {
             return nullObjectReturn("OOM in encodedFormatToString()");
         }
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index 627f8f5..a900286 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -475,7 +475,7 @@
 
 static jstring ImageDecoder_nGetMimeType(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
     auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
-    return encodedFormatToString(env, decoder->mCodec->getEncodedFormat());
+    return getMimeTypeAsJavaString(env, decoder->mCodec->getEncodedFormat());
 }
 
 static jobject ImageDecoder_nGetColorSpace(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/core/jni/android/graphics/MimeType.h
similarity index 85%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to core/jni/android/graphics/MimeType.h
index fb5d836..38a579c 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/core/jni/android/graphics/MimeType.h
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
-package android.media;
+#pragma once
 
-parcelable RouteSessionInfo;
+#include "SkEncodedImageFormat.h"
+
+const char* getMimeType(SkEncodedImageFormat);
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 79cf019..53327bc 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2312,6 +2312,48 @@
     return jStatus;
 }
 
+static jint
+android_media_AudioSystem_getDevicesForAttributes(JNIEnv *env, jobject thiz,
+        jobject jaa, jobjectArray jDeviceArray)
+{
+    const jsize maxResultSize = env->GetArrayLength(jDeviceArray);
+    // the JNI is always expected to provide us with an array capable of holding enough
+    // devices i.e. the most we ever route a track to. This is preferred over receiving an ArrayList
+    // with reverse JNI to make the array grow as need as this would be less efficient, and some
+    // components call this method often
+    if (jDeviceArray == nullptr || maxResultSize == 0) {
+        ALOGE("%s invalid array to store AudioDeviceAddress", __FUNCTION__);
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+
+    JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
+    jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
+    if (jStatus != (jint) AUDIO_JAVA_SUCCESS) {
+        return jStatus;
+    }
+
+    AudioDeviceTypeAddrVector devices;
+    jStatus = check_AudioSystem_Command(
+            AudioSystem::getDevicesForAttributes(*(paa.get()), &devices));
+    if (jStatus != NO_ERROR) {
+        return jStatus;
+    }
+
+    if (devices.size() > maxResultSize) {
+        return AUDIO_JAVA_INVALID_OPERATION;
+    }
+    size_t index = 0;
+    jobject jAudioDeviceAddress = NULL;
+    for (const auto& device : devices) {
+        jStatus = createAudioDeviceAddressFromNative(env, &jAudioDeviceAddress, &device);
+        if (jStatus != AUDIO_JAVA_SUCCESS) {
+            return jStatus;
+        }
+        env->SetObjectArrayElement(jDeviceArray, index++, jAudioDeviceAddress);
+    }
+    return jStatus;
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gMethods[] = {
@@ -2395,6 +2437,7 @@
     {"setPreferredDeviceForStrategy", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setPreferredDeviceForStrategy},
     {"removePreferredDeviceForStrategy", "(I)I", (void *)android_media_AudioSystem_removePreferredDeviceForStrategy},
     {"getPreferredDeviceForStrategy", "(I[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getPreferredDeviceForStrategy},
+    {"getDevicesForAttributes", "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getDevicesForAttributes}
 };
 
 static const JNINativeMethod gEventHandlerMethods[] = {
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index ff596b4..062b886 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -220,8 +220,12 @@
 
 // ----------------------------------------------------------------------------
 
+static std::unique_ptr<DynamicLibManager> sDynamicLibManager =
+    std::make_unique<DynamicLibManager>();
+
 // Let the opaque type AAssetManager refer to a guarded AssetManager2 instance.
 struct GuardedAssetManager : public ::AAssetManager {
+  GuardedAssetManager() : guarded_assetmanager(sDynamicLibManager.get()) {}
   Guarded<AssetManager2> guarded_assetmanager;
 };
 
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index d17d0a4..466544c 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -319,7 +319,8 @@
   MOUNT_EXTERNAL_INSTALLER = 5,
   MOUNT_EXTERNAL_FULL = 6,
   MOUNT_EXTERNAL_PASS_THROUGH = 7,
-  MOUNT_EXTERNAL_COUNT = 8
+  MOUNT_EXTERNAL_ANDROID_WRITABLE = 8,
+  MOUNT_EXTERNAL_COUNT = 9
 };
 
 // The order of entries here must be kept in sync with MountExternalKind enum values.
@@ -331,6 +332,8 @@
   "/mnt/runtime/write",   // MOUNT_EXTERNAL_LEGACY
   "/mnt/runtime/write",   // MOUNT_EXTERNAL_INSTALLER
   "/mnt/runtime/full",    // MOUNT_EXTERNAL_FULL
+  "/mnt/runtime/full",    // MOUNT_EXTERNAL_PASS_THROUGH (only used w/ FUSE)
+  "/mnt/runtime/full",    // MOUNT_EXTERNAL_ANDROID_WRITABLE (only used w/ FUSE)
 };
 
 // Must match values in com.android.internal.os.Zygote.
@@ -743,9 +746,6 @@
 
   const userid_t user_id = multiuser_get_user_id(uid);
   const std::string user_source = StringPrintf("/mnt/user/%d", user_id);
-  const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
-  bool isFuse = GetBoolProperty(kPropFuse, false);
-
   // Shell is neither AID_ROOT nor AID_EVERYBODY. Since it equally needs 'execute' access to
   // /mnt/user/0 to 'adb shell ls /sdcard' for instance, we set the uid bit of /mnt/user/0 to
   // AID_SHELL. This gives shell access along with apps running as group everybody (user 0 apps)
@@ -754,14 +754,15 @@
   PrepareDir(user_source, 0710, user_id ? AID_ROOT : AID_SHELL,
              multiuser_get_uid(user_id, AID_EVERYBODY), fail_fn);
 
+  bool isFuse = GetBoolProperty(kPropFuse, false);
+
   if (isFuse) {
-    if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH || mount_mode ==
-        MOUNT_EXTERNAL_INSTALLER || mount_mode == MOUNT_EXTERNAL_FULL) {
-      // For now, MediaProvider, installers and "full" get the pass_through mount
-      // view, which is currently identical to the sdcardfs write view.
-      //
-      // TODO(b/146189163): scope down MOUNT_EXTERNAL_INSTALLER
+    if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH) {
+      const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
       BindMount(pass_through_source, "/storage", fail_fn);
+    } else if (mount_mode == MOUNT_EXTERNAL_INSTALLER) {
+      const std::string installer_source = StringPrintf("/mnt/installer/%d", user_id);
+      BindMount(installer_source, "/storage", fail_fn);
     } else {
       BindMount(user_source, "/storage", fail_fn);
     }
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 5624f45..082a289 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -33,27 +33,27 @@
 
 // Static whitelist of open paths that the zygote is allowed to keep open.
 static const char* kPathWhitelist[] = {
-  "/apex/com.android.appsearch/javalib/framework-appsearch.jar",
-  "/apex/com.android.conscrypt/javalib/conscrypt.jar",
-  "/apex/com.android.ipsec/javalib/ike.jar",
-  "/apex/com.android.media/javalib/updatable-media.jar",
-  "/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar",
-  "/apex/com.android.os.statsd/javalib/framework-statsd.jar",
-  "/apex/com.android.sdkext/javalib/framework-sdkext.jar",
-  "/apex/com.android.wifi/javalib/framework-wifi.jar",
-  "/apex/com.android.tethering/javalib/framework-tethering.jar",
-  "/dev/null",
-  "/dev/socket/zygote",
-  "/dev/socket/zygote_secondary",
-  "/dev/socket/usap_pool_primary",
-  "/dev/socket/usap_pool_secondary",
-  "/dev/socket/webview_zygote",
-  "/dev/socket/heapprofd",
-  "/sys/kernel/debug/tracing/trace_marker",
-  "/system/framework/framework-res.apk",
-  "/dev/urandom",
-  "/dev/ion",
-  "/dev/dri/renderD129", // Fixes b/31172436
+        "/apex/com.android.appsearch/javalib/framework-appsearch.jar",
+        "/apex/com.android.conscrypt/javalib/conscrypt.jar",
+        "/apex/com.android.ipsec/javalib/ike.jar",
+        "/apex/com.android.media/javalib/updatable-media.jar",
+        "/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar",
+        "/apex/com.android.os.statsd/javalib/framework-statsd.jar",
+        "/apex/com.android.sdkext/javalib/framework-sdkextensions.jar",
+        "/apex/com.android.wifi/javalib/framework-wifi.jar",
+        "/apex/com.android.tethering/javalib/framework-tethering.jar",
+        "/dev/null",
+        "/dev/socket/zygote",
+        "/dev/socket/zygote_secondary",
+        "/dev/socket/usap_pool_primary",
+        "/dev/socket/usap_pool_secondary",
+        "/dev/socket/webview_zygote",
+        "/dev/socket/heapprofd",
+        "/sys/kernel/debug/tracing/trace_marker",
+        "/system/framework/framework-res.apk",
+        "/dev/urandom",
+        "/dev/ion",
+        "/dev/dri/renderD129", // Fixes b/31172436
 };
 
 static const char kFdPath[] = "/proc/self/fd";
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index d5a3b5e..cd3887e 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -702,6 +702,12 @@
     // CATEGORY: SETTINGS
     // OS: R
     ACTION_DASHBOARD_VISIBLE_TIME = 1729;
+
+    // ACTION: Allow "Access all files" for an app
+    APP_SPECIAL_PERMISSION_MANAGE_EXT_STRG_ALLOW = 1730;
+
+    // ACTION: Deny "Access all files" for an app
+    APP_SPECIAL_PERMISSION_MANAGE_EXT_STRG_DENY = 1731;
 }
 
 /**
@@ -2542,4 +2548,13 @@
     // OS: R
     FUELGAUGE_BATTERY_SHARE = 1821;
 
+    // OPEN: Settings -> Apps & Notifications -> Special App Access
+    // CATEGORY: SETTINGS
+    // OS: R
+    MANAGE_EXTERNAL_STORAGE = 1822;
+
+    // Open: Settings > DND > People
+    // OS: R
+    DND_PEOPLE = 1823;
+
 }
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
index 557075c..2de5b7f 100644
--- a/core/proto/android/service/graphicsstats.proto
+++ b/core/proto/android/service/graphicsstats.proto
@@ -32,6 +32,10 @@
 }
 
 message GraphicsStatsProto {
+    enum PipelineType {
+        GL = 0;
+        VULKAN = 1;
+    }
     option (android.msg_privacy).dest = DEST_AUTOMATIC;
 
     // The package name of the app
@@ -54,6 +58,9 @@
 
     // The gpu frame time histogram for the package
     repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7;
+
+    // HWUI renders pipeline type: GL or Vulkan
+    optional PipelineType pipeline = 8;
 }
 
 message GraphicsStatsJankSummaryProto {
diff --git a/core/proto/android/stats/launcher/Android.bp b/core/proto/android/stats/launcher/Android.bp
index b8fb6ff..976a0b8 100644
--- a/core/proto/android/stats/launcher/Android.bp
+++ b/core/proto/android/stats/launcher/Android.bp
@@ -25,3 +25,16 @@
         "*.proto",
     ],
 }
+
+java_library {
+    name: "launcherprotoslite",
+    proto: {
+        type: "lite",
+        include_dirs: ["external/protobuf/src"],
+    },
+
+    sdk_version: "current",
+    srcs: [
+        "*.proto",
+    ],
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5957963..d4768c0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -370,6 +370,7 @@
     <protected-broadcast android:name="android.net.wifi.STATE_CHANGE" />
     <protected-broadcast android:name="android.net.wifi.LINK_CONFIGURATION_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.CONFIGURED_NETWORKS_CHANGE" />
+    <protected-broadcast android:name="android.net.wifi.action.NETWORK_SETTINGS_RESET" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_ICON" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST" />
@@ -457,7 +458,6 @@
     <protected-broadcast android:name="android.intent.action.internal_sim_state_changed" />
     <protected-broadcast android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
     <protected-broadcast android:name="android.intent.action.PRECISE_CALL_STATE" />
-    <protected-broadcast android:name="android.intent.action.PRECISE_DATA_CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.intent.action.SUBSCRIPTION_PHONE_STATE" />
     <protected-broadcast android:name="android.intent.action.USER_INFO_CHANGED" />
     <protected-broadcast android:name="android.intent.action.USER_UNLOCKED" />
@@ -641,10 +641,6 @@
 
     <protected-broadcast android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY" />
 
-    <!-- NETWORK_SET_TIME moved from com.android.phone to system server. It should ultimately be
-         removed. -->
-    <protected-broadcast android:name="android.telephony.action.NETWORK_SET_TIME" />
-
     <!-- For tether entitlement recheck-->
     <protected-broadcast
         android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" />
@@ -935,11 +931,10 @@
 
     <!-- Allows an application a broad access to external storage in scoped storage.
          Intended to be used by few apps that need to manage files on behalf of the users.
-         <p>Protection level: signature|appop
-         <p>This protection level is temporary and will most likely be changed to |preinstalled -->
+         <p>Protection level: signature|appop|preinstalled -->
     <permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
         android:permissionGroup="android.permission-group.UNDEFINED"
-        android:protectionLevel="signature|appop" />
+        android:protectionLevel="signature|appop|preinstalled" />
 
     <!-- ====================================================================== -->
     <!-- Permissions for accessing the device location                          -->
@@ -1657,6 +1652,7 @@
     <!-- Allows holder to request bluetooth/wifi scan bypassing global "use location" setting and
          location permissions.
          <p>Not for use by third-party or privileged applications.
+         @SystemApi
          @hide
     -->
     <permission android:name="android.permission.RADIO_SCAN_WITHOUT_LOCATION"
@@ -1792,6 +1788,11 @@
         android:label="@string/permlab_preferredPaymentInfo"
         android:protectionLevel="normal" />
 
+    <!-- @SystemApi Allows an internal user to use privileged SecureElement APIs.
+         @hide -->
+    <permission android:name="android.permission.SECURE_ELEMENT_PRIVILEGED"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @deprecated This permission used to allow too broad access to sensitive methods and all its
          uses have been replaced by a more appropriate permission. Most uses have been replaced with
          a NETWORK_STACK or NETWORK_SETTINGS check. Please look up the documentation of the
@@ -2072,8 +2073,10 @@
         android:protectionLevel="signature|privileged" />
 
     <!-- Allows read only access to precise phone state.
-         @hide Pending API council approval -->
+         Allows reading of detailed information about phone state for special-use applications
+         such as dialers, carrier applications, or ims applications. -->
     <permission android:name="android.permission.READ_PRECISE_PHONE_STATE"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:protectionLevel="signature|privileged" />
 
     <!-- @SystemApi Allows read access to privileged phone state.
@@ -4758,6 +4761,10 @@
     <!-- Allows input events to be monitored. Very dangerous!  @hide -->
     <permission android:name="android.permission.MONITOR_INPUT"
                 android:protectionLevel="signature" />
+    <!--  Allows the caller to change the associations between input devices and displays.
+        Very dangerous! @hide -->
+    <permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT"
+                android:protectionLevel="signature" />
 
     <!-- Allows query of any normal app on the device, regardless of manifest declarations. -->
     <permission android:name="android.permission.QUERY_ALL_PACKAGES"
diff --git a/core/res/res/layout/accessibility_button_chooser_item.xml b/core/res/res/layout/accessibility_button_chooser_item.xml
index 1edd2cd..d19e313 100644
--- a/core/res/res/layout/accessibility_button_chooser_item.xml
+++ b/core/res/res/layout/accessibility_button_chooser_item.xml
@@ -36,7 +36,7 @@
         android:id="@+id/accessibility_button_target_label"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginStart="8dp"
+        android:layout_marginStart="14dp"
         android:layout_weight="1"
         android:textColor="?attr/textColorPrimary"/>
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6691d4c..6f554f02 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1877,6 +1877,8 @@
     <string name="config_defaultCallRedirection" translatable="false"></string>
     <!-- The name of the package that will hold the call screening role by default. -->
     <string name="config_defaultCallScreening" translatable="false"></string>
+    <!-- The name of the package that will hold the system gallery role. -->
+    <string name="config_systemGallery" translatable="false">com.android.gallery</string>
 
     <!-- Enable/disable default bluetooth profiles:
         HSP_AG, ObexObjectPush, Audio, NAP -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 817ccde..6cf6a68 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3000,7 +3000,6 @@
     <public-group type="attr" first-id="0x01010607">
       <public name="importantForContentCapture" />
       <public name="forceQueryable" />
-      <!-- @hide @SystemApi -->
       <public name="resourcesMap" />
       <public name="animatedImageDrawable"/>
       <public name="htmlDescription"/>
@@ -3031,6 +3030,8 @@
       <public name="config_defaultCallRedirection" />
       <!-- @hide @SystemApi -->
       <public name="config_defaultCallScreening" />
+      <!-- @hide @SystemApi @TestApi -->
+      <public name="config_systemGallery" />
     </public-group>
 
     <public-group type="bool" first-id="0x01110005">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 66267d1..a0e4064 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4358,10 +4358,7 @@
     <string name="accessibility_shortcut_menu_button">Empty</string>
 
     <!-- Text in button that edit the accessibility shortcut menu. [CHAR LIMIT=100] -->
-    <string name="edit_accessibility_shortcut_menu_button">Edit</string>
-
-    <!-- Text in button that save the accessibility shortcut menu changed status. [CHAR LIMIT=100] -->
-    <string name="save_accessibility_shortcut_menu_button">Save</string>
+    <string name="edit_accessibility_shortcut_menu_button">Edit shortcuts</string>
 
     <!-- Text in button that cancel the accessibility shortcut menu changed status. [CHAR LIMIT=100] -->
     <string name="cancel_accessibility_shortcut_menu_button">Cancel</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 01bd510..9e11749 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3212,7 +3212,6 @@
   <java-symbol type="id" name="accessibility_button_target_switch_item" />
   <java-symbol type="string" name="accessibility_magnification_chooser_text" />
   <java-symbol type="string" name="edit_accessibility_shortcut_menu_button" />
-  <java-symbol type="string" name="save_accessibility_shortcut_menu_button" />
   <java-symbol type="string" name="cancel_accessibility_shortcut_menu_button" />
 
   <java-symbol type="drawable" name="ic_accessibility_magnification" />
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/WifiConfigurationHelper.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/WifiConfigurationHelper.java
index f0a8367..a296ca2 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/WifiConfigurationHelper.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/WifiConfigurationHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.connectivitymanagertest;
 
+import android.net.IpConfiguration;
 import android.net.IpConfiguration.IpAssignment;
 import android.net.IpConfiguration.ProxySettings;
 import android.net.LinkAddress;
@@ -136,7 +137,7 @@
         config.enterpriseConfig.setPhase2Method(phase2);
         config.enterpriseConfig.setIdentity(identity);
         config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity);
-        config.enterpriseConfig.setCaCertificateAlias(caCert);
+        config.enterpriseConfig.setCaCertificateAliases(new String[] {caCert});
         config.enterpriseConfig.setClientCertificateAlias(clientCert);
         return config;
     }
@@ -147,8 +148,12 @@
     private static WifiConfiguration createGenericConfig(String ssid) {
         WifiConfiguration config = new WifiConfiguration();
         config.SSID = quotedString(ssid);
-        config.setIpAssignment(IpAssignment.DHCP);
-        config.setProxySettings(ProxySettings.NONE);
+
+        IpConfiguration ipConfiguration = config.getIpConfiguration();
+        ipConfiguration.setIpAssignment(IpAssignment.DHCP);
+        ipConfiguration.setProxySettings(ProxySettings.NONE);
+        config.setIpConfiguration(ipConfiguration);
+
         return config;
     }
 
@@ -237,6 +242,7 @@
                 throw new IllegalArgumentException();
         }
 
+        IpConfiguration ipConfiguration = config.getIpConfiguration();
         if (jsonConfig.has("ip")) {
             StaticIpConfiguration staticIpConfig = new StaticIpConfiguration();
 
@@ -247,13 +253,14 @@
             staticIpConfig.dnsServers.add(getInetAddress(jsonConfig.getString("dns1")));
             staticIpConfig.dnsServers.add(getInetAddress(jsonConfig.getString("dns2")));
 
-            config.setIpAssignment(IpAssignment.STATIC);
-            config.setStaticIpConfiguration(staticIpConfig);
+            ipConfiguration.setIpAssignment(IpAssignment.STATIC);
+            ipConfiguration.setStaticIpConfiguration(staticIpConfig);
         } else {
-            config.setIpAssignment(IpAssignment.DHCP);
+            ipConfiguration.setIpAssignment(IpAssignment.DHCP);
         }
+        ipConfiguration.setProxySettings(ProxySettings.NONE);
+        config.setIpConfiguration(ipConfiguration);
 
-        config.setProxySettings(ProxySettings.NONE);
         return config;
     }
 
diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
index 2989df8..5d42915 100644
--- a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
+++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
@@ -268,7 +268,7 @@
         File snd_stat = new File (root_filepath + "tcp_snd");
         int tx = BandwidthTestUtil.parseIntValueFromFile(snd_stat);
         NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
-        stats.addValues(NetworkStats.IFACE_ALL, uid, NetworkStats.SET_DEFAULT,
+        stats.addEntry(NetworkStats.IFACE_ALL, uid, NetworkStats.SET_DEFAULT,
                 NetworkStats.TAG_NONE, rx, 0, tx, 0, 0);
         return stats;
     }
diff --git a/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java b/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java
index 707d7b3..239f971 100644
--- a/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java
+++ b/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java
@@ -54,7 +54,7 @@
             recycle.txBytes = 150000;
             recycle.txPackets = 1500;
             recycle.operations = 0;
-            mNetworkStats.addValues(recycle);
+            mNetworkStats.addEntry(recycle);
             if (recycle.set == 1) {
                 uid++;
             }
@@ -70,7 +70,7 @@
             recycle.txBytes = 180000 * mSize;
             recycle.txPackets = 1200 * mSize;
             recycle.operations = 0;
-            mNetworkStats.addValues(recycle);
+            mNetworkStats.addEntry(recycle);
         }
     }
 
diff --git a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
index 68b9b00..f4709ff 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
@@ -33,14 +33,15 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.support.test.uiautomator.UiDevice;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
 
-import libcore.io.Streams;
-
 import com.google.mockwebserver.MockResponse;
 import com.google.mockwebserver.MockWebServer;
 
+import libcore.io.Streams;
+
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.File;
@@ -63,6 +64,7 @@
     private static final String TAG = "DownloadManagerBaseTest";
     protected DownloadManager mDownloadManager = null;
     private MockWebServer mServer = null;
+    private UiDevice mUiDevice = null;
     protected String mFileType = "text/plain";
     protected Context mContext = null;
     protected MultipleDownloadsCompletedReceiver mReceiver = null;
@@ -234,6 +236,7 @@
     @Override
     public void setUp() throws Exception {
         mContext = getInstrumentation().getContext();
+        mUiDevice = UiDevice.getInstance(getInstrumentation());
         mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE);
         mServer = new MockWebServer();
         mServer.play();
@@ -512,7 +515,7 @@
         Log.i(LOG_TAG, "Setting WiFi State to: " + enable);
         WifiManager manager = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE);
 
-        manager.setWifiEnabled(enable);
+        mUiDevice.executeShellCommand("svc wifi " + (enable ? "enable" : "disable"));
 
         String timeoutMessage = "Timed out waiting for Wifi to be "
             + (enable ? "enabled!" : "disabled!");
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index beaaa37..d8b527c 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -438,8 +438,7 @@
     }
 
     private static ClientTransaction newStopTransaction(Activity activity) {
-        final StopActivityItem stopStateRequest =
-                StopActivityItem.obtain(false /* showWindow */, 0 /* configChanges */);
+        final StopActivityItem stopStateRequest = StopActivityItem.obtain(0 /* configChanges */);
 
         final ClientTransaction transaction = newTransaction(activity);
         transaction.setLifecycleStateRequest(stopStateRequest);
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 37d21f0..4b29d59 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -274,30 +274,15 @@
 
     @Test
     public void testRecycleStopItem() {
-        StopActivityItem emptyItem = StopActivityItem.obtain(false, 0);
-        StopActivityItem item = StopActivityItem.obtain(true, 4);
+        StopActivityItem emptyItem = StopActivityItem.obtain(0);
+        StopActivityItem item = StopActivityItem.obtain(4);
         assertNotSame(item, emptyItem);
         assertFalse(item.equals(emptyItem));
 
         item.recycle();
         assertEquals(item, emptyItem);
 
-        StopActivityItem item2 = StopActivityItem.obtain(true, 3);
-        assertSame(item, item2);
-        assertFalse(item2.equals(emptyItem));
-    }
-
-    @Test
-    public void testRecycleWindowVisibleItem() {
-        WindowVisibilityItem emptyItem = WindowVisibilityItem.obtain(false);
-        WindowVisibilityItem item = WindowVisibilityItem.obtain(true);
-        assertNotSame(item, emptyItem);
-        assertFalse(item.equals(emptyItem));
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        WindowVisibilityItem item2 = WindowVisibilityItem.obtain(true);
+        StopActivityItem item2 = StopActivityItem.obtain(3);
         assertSame(item, item2);
         assertFalse(item2.equals(emptyItem));
     }
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 39bf742..ecea901 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -61,6 +61,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -180,31 +181,6 @@
     }
 
     @Test
-    public void testWindowVisibilityChange() {
-        // Write to parcel
-        WindowVisibilityItem item = WindowVisibilityItem.obtain(true /* showWindow */);
-        writeAndPrepareForReading(item);
-
-        // Read from parcel and assert
-        WindowVisibilityItem result = WindowVisibilityItem.CREATOR.createFromParcel(mParcel);
-
-        assertEquals(item.hashCode(), result.hashCode());
-        assertTrue(item.equals(result));
-
-        // Check different value
-        item = WindowVisibilityItem.obtain(false);
-
-        mParcel = Parcel.obtain();
-        writeAndPrepareForReading(item);
-
-        // Read from parcel and assert
-        result = WindowVisibilityItem.CREATOR.createFromParcel(mParcel);
-
-        assertEquals(item.hashCode(), result.hashCode());
-        assertTrue(item.equals(result));
-    }
-
-    @Test
     public void testDestroy() {
         DestroyActivityItem item = DestroyActivityItem.obtain(true /* finished */,
                 135 /* configChanges */);
@@ -299,8 +275,7 @@
     @Test
     public void testStop() {
         // Write to parcel
-        StopActivityItem item = StopActivityItem.obtain(true /* showWindow */,
-                14 /* configChanges */);
+        StopActivityItem item = StopActivityItem.obtain(14 /* configChanges */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -311,14 +286,26 @@
     }
 
     @Test
+    public void testStart() {
+        // Write to parcel
+        StartActivityItem item = StartActivityItem.obtain();
+        writeAndPrepareForReading(item);
+
+        // Read from parcel and assert
+        StartActivityItem result = StartActivityItem.CREATOR.createFromParcel(mParcel);
+
+        assertEquals(item.hashCode(), result.hashCode());
+        assertEquals(item, result);
+    }
+
+    @Test
     public void testClientTransaction() {
         // Write to parcel
-        WindowVisibilityItem callback1 = WindowVisibilityItem.obtain(true);
+        NewIntentItem callback1 = NewIntentItem.obtain(new ArrayList<>(), true);
         ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
                 config());
 
-        StopActivityItem lifecycleRequest = StopActivityItem.obtain(true /* showWindow */,
-                78 /* configChanges */);
+        StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */);
 
         IApplicationThread appThread = new StubAppThread();
         Binder activityToken = new Binder();
@@ -340,7 +327,7 @@
     @Test
     public void testClientTransactionCallbacksOnly() {
         // Write to parcel
-        WindowVisibilityItem callback1 = WindowVisibilityItem.obtain(true);
+        NewIntentItem callback1 = NewIntentItem.obtain(new ArrayList<>(), true);
         ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
                 config());
 
@@ -363,8 +350,7 @@
     @Test
     public void testClientTransactionLifecycleOnly() {
         // Write to parcel
-        StopActivityItem lifecycleRequest = StopActivityItem.obtain(true /* showWindow */,
-                78 /* configChanges */);
+        StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */);
 
         IApplicationThread appThread = new StubAppThread();
         Binder activityToken = new Binder();
diff --git a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
index de6f8f7..750ffa1 100644
--- a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
@@ -22,7 +22,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import org.junit.Test;
 
diff --git a/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java
index 9b3d0c9..b88c36f 100644
--- a/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java
@@ -22,7 +22,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import org.junit.Test;
 
diff --git a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
index bee270e..ba29a97 100644
--- a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
@@ -22,7 +22,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import org.junit.Test;
 
diff --git a/core/tests/coretests/src/android/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java
index c6c0b46..b13bcd1 100644
--- a/core/tests/coretests/src/android/net/NetworkKeyTest.java
+++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.net.wifi.ScanResult;
@@ -107,7 +108,7 @@
 
     @Test
     public void createFromScanResult_nullSsid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.BSSID = VALID_BSSID;
 
         assertNull(NetworkKey.createFromScanResult(scanResult));
@@ -115,7 +116,7 @@
 
     @Test
     public void createFromScanResult_emptySsid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = "";
         scanResult.BSSID = VALID_BSSID;
 
@@ -124,7 +125,7 @@
 
     @Test
     public void createFromScanResult_noneSsid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = WifiManager.UNKNOWN_SSID;
         scanResult.BSSID = VALID_BSSID;
 
@@ -133,7 +134,7 @@
 
     @Test
     public void createFromScanResult_nullBssid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = VALID_UNQUOTED_SSID;
 
         assertNull(NetworkKey.createFromScanResult(scanResult));
@@ -141,7 +142,7 @@
 
     @Test
     public void createFromScanResult_emptyBssid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = VALID_UNQUOTED_SSID;
         scanResult.BSSID = "";
 
@@ -150,7 +151,7 @@
 
     @Test
     public void createFromScanResult_invalidBssid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = VALID_UNQUOTED_SSID;
         scanResult.BSSID = INVALID_BSSID;
 
@@ -159,7 +160,7 @@
 
     @Test
     public void createFromScanResult_validSsid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = VALID_UNQUOTED_SSID;
         scanResult.BSSID = VALID_BSSID;
 
diff --git a/core/tests/coretests/src/android/util/TimestampedValueTest.java b/core/tests/coretests/src/android/os/TimestampedValueTest.java
similarity index 98%
rename from core/tests/coretests/src/android/util/TimestampedValueTest.java
rename to core/tests/coretests/src/android/os/TimestampedValueTest.java
index 6fc2400..f36d9e6 100644
--- a/core/tests/coretests/src/android/util/TimestampedValueTest.java
+++ b/core/tests/coretests/src/android/os/TimestampedValueTest.java
@@ -14,14 +14,12 @@
  * limitations under the License.
  */
 
-package android.util;
+package android.os;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.fail;
 
-import android.os.Parcel;
-
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 0e19ca8..d0fd92a 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -90,12 +90,12 @@
             mImeConsumer.onWindowFocusGained();
             mImeConsumer.applyImeVisibility(true);
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test if setVisibility can hide IME
             mImeConsumer.applyImeVisibility(false);
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
         });
     }
 
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 179929f..fa61a0a 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -16,11 +16,11 @@
 
 package android.view;
 
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.WindowInsets.Type.systemBars;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -39,8 +39,6 @@
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.test.InsetsModeSession;
 
-import androidx.test.runner.AndroidJUnit4;
-
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -52,6 +50,8 @@
 
 import java.util.List;
 
+import androidx.test.runner.AndroidJUnit4;
+
 /**
  * Tests for {@link InsetsAnimationControlImpl}.
  *
@@ -116,7 +116,7 @@
         mController = new InsetsAnimationControlImpl(controls,
                 new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
                 mMockController, 10 /* durationMs */,
-                false /* fade */);
+                false /* fade */, LAYOUT_INSETS_DURING_ANIMATION_SHOWN);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index a89fc1e..1db96b1 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -68,6 +68,7 @@
     private InsetsController mController;
     private SurfaceSession mSession = new SurfaceSession();
     private SurfaceControl mLeash;
+    private ViewRootImpl mViewRoot;
 
     @Before
     public void setup() {
@@ -77,13 +78,13 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             Context context = InstrumentationRegistry.getTargetContext();
             // cannot mock ViewRootImpl since it's final.
-            ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay());
+            mViewRoot = new ViewRootImpl(context, context.getDisplay());
             try {
-                viewRootImpl.setView(new TextView(context), new LayoutParams(), null);
+                mViewRoot.setView(new TextView(context), new LayoutParams(), null);
             } catch (BadTokenException e) {
                 // activity isn't running, we will ignore BadTokenException.
             }
-            mController = new InsetsController(viewRootImpl);
+            mController = new InsetsController(mViewRoot);
             final Rect rect = new Rect(5, 5, 5, 5);
             mController.calculateInsets(
                     false,
@@ -117,16 +118,22 @@
 
     @Test
     public void testControlsRevoked_duringAnim() {
-        InsetsSourceControl control =
-                new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point());
-        mController.onControlsChanged(new InsetsSourceControl[] { control });
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            InsetsSourceControl control =
+                    new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point());
+            mController.onControlsChanged(new InsetsSourceControl[] { control });
 
-        WindowInsetsAnimationControlListener mockListener =
-                mock(WindowInsetsAnimationControlListener.class);
-        mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */, mockListener);
-        verify(mockListener).onReady(any(), anyInt());
-        mController.onControlsChanged(new InsetsSourceControl[0]);
-        verify(mockListener).onCancelled();
+            WindowInsetsAnimationControlListener mockListener =
+                    mock(WindowInsetsAnimationControlListener.class);
+            mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */,
+                    mockListener);
+
+            // Ready gets deferred until next predraw
+            mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
+            verify(mockListener).onReady(any(), anyInt());
+            mController.onControlsChanged(new InsetsSourceControl[0]);
+            verify(mockListener).onCancelled();
+        });
     }
 
     @Test
@@ -154,16 +161,16 @@
             mController.show(Type.all());
             // quickly jump to final state by cancelling it.
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             mController.applyImeVisibility(false /* setVisible */);
             mController.hide(Type.all());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -180,10 +187,10 @@
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained();
             mController.applyImeVisibility(true);
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
             mController.applyImeVisibility(false);
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -201,16 +208,16 @@
             // test show select types.
             mController.show(types);
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test hide all
             mController.hide(types);
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -227,29 +234,29 @@
             // test show select types.
             mController.show(types);
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test hide all
             mController.hide(Type.all());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test single show
             mController.show(Type.navigationBars());
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test single hide
             mController.hide(Type.navigationBars());
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -267,31 +274,31 @@
             mController.show(Type.navigationBars());
             mController.show(Type.systemBars());
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             mController.hide(Type.navigationBars());
             mController.hide(Type.systemBars());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             int types = Type.navigationBars() | Type.systemBars();
             // show two at a time and hide one by one.
             mController.show(types);
             mController.hide(Type.navigationBars());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             mController.hide(Type.systemBars());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -309,15 +316,15 @@
             mController.show(types);
             mController.hide(Type.navigationBars());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             mController.hide(Type.systemBars());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -336,12 +343,16 @@
 
             ArgumentCaptor<WindowInsetsAnimationController> controllerCaptor =
                     ArgumentCaptor.forClass(WindowInsetsAnimationController.class);
+
+            // Ready gets deferred until next predraw
+            mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
+
             verify(mockListener).onReady(controllerCaptor.capture(), anyInt());
             controllerCaptor.getValue().finish(false /* shown */);
         });
         waitUntilNextFrame();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isVisible());
+            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 7af833b..492c036 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -96,7 +96,7 @@
     @Test
     public void testHide() {
         mConsumer.hide();
-        assertFalse("Consumer should not be visible", mConsumer.isVisible());
+        assertFalse("Consumer should not be visible", mConsumer.isRequestedVisible());
         verify(mSpyInsetsSource).setVisible(eq(false));
     }
 
@@ -106,7 +106,7 @@
         // Insets source starts out visible
         mConsumer.hide();
         mConsumer.show();
-        assertTrue("Consumer should be visible", mConsumer.isVisible());
+        assertTrue("Consumer should be visible", mConsumer.isRequestedVisible());
         verify(mSpyInsetsSource).setVisible(eq(false));
         verify(mSpyInsetsSource).setVisible(eq(true));
     }
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index d7f50ba..e3b08bb 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -108,5 +108,30 @@
         assertEquals(Insets.of(0, 100, 0, 0), insets);
     }
 
+    @Test
+    public void testCalculateVisibleInsets_default() {
+        mSource.setFrame(new Rect(0, 0, 500, 100));
+        Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+        assertEquals(Insets.of(0, 100, 0, 0), insets);
+    }
+
+    @Test
+    public void testCalculateVisibleInsets_override() {
+        mSource.setFrame(new Rect(0, 0, 500, 100));
+        mSource.setVisibleFrame(new Rect(0, 0, 500, 200));
+        Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+        assertEquals(Insets.of(0, 200, 0, 0), insets);
+    }
+
+    @Test
+    public void testCalculateVisibleInsets_invisible() {
+        mSource.setFrame(new Rect(0, 0, 500, 100));
+        mSource.setVisibleFrame(new Rect(0, 0, 500, 200));
+        mSource.setVisible(false);
+        Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+        assertEquals(Insets.of(0, 0, 0, 0), insets);
+    }
+
+
     // Parcel and equals already tested via InsetsStateTest
 }
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 6062088..4b76fee 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -18,11 +18,14 @@
 
 import static android.view.InsetsState.ISIDE_BOTTOM;
 import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES;
 import static android.view.InsetsState.ITYPE_CAPTION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 
 import static org.junit.Assert.assertEquals;
@@ -116,14 +119,18 @@
 
     @Test
     public void testCalculateInsets_imeIgnoredWithoutAdjustResize() {
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
-        mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
-        mState.getSource(ITYPE_IME).setVisible(true);
-        WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
-                DisplayCutout.NO_CUTOUT, null, null, 0, null);
-        assertEquals(0, insets.getSystemWindowInsetBottom());
-        assertTrue(insets.isVisible(ime()));
+        try (final InsetsModeSession session =
+                     new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
+            mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+            mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+            mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+            mState.getSource(ITYPE_IME).setVisible(true);
+            WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
+                    DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_NOTHING, null);
+            assertEquals(0, insets.getSystemWindowInsetBottom());
+            assertEquals(100, insets.getInsets(ime()).bottom);
+            assertTrue(insets.isVisible(ime()));
+        }
     }
 
     @Test
@@ -204,6 +211,42 @@
         assertFalse(InsetsState.getDefaultVisibility(ITYPE_IME));
     }
 
+    @Test
+    public void testCalculateVisibleInsets() throws Exception {
+        try (final InsetsModeSession session =
+                     new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
+            mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+            mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+            mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+            mState.getSource(ITYPE_IME).setVisible(true);
+
+            // Make sure bottom gestures are ignored
+            mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
+            mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+            Rect visibleInsets = mState.calculateVisibleInsets(
+                    new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_PAN);
+            assertEquals(new Rect(0, 100, 0, 100), visibleInsets);
+        }
+    }
+
+    @Test
+    public void testCalculateVisibleInsets_adjustNothing() throws Exception {
+        try (final InsetsModeSession session =
+                     new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
+            mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+            mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+            mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+            mState.getSource(ITYPE_IME).setVisible(true);
+
+            // Make sure bottom gestures are ignored
+            mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
+            mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+            Rect visibleInsets = mState.calculateVisibleInsets(
+                    new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_NOTHING);
+            assertEquals(new Rect(0, 100, 0, 0), visibleInsets);
+        }
+    }
+
     private void assertEqualsAndHashCode() {
         assertEquals(mState, mState2);
         assertEquals(mState.hashCode(), mState2.hashCode());
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index 8c7b28a..e5a4f6d 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -63,7 +63,8 @@
         b.setInsets(navigationBars(), Insets.of(0, 0, 0, 100));
         b.setInsets(ime(), Insets.of(0, 0, 0, 300));
         WindowInsets insets = b.build();
-        assertEquals(300, insets.getSystemWindowInsets().bottom);
+        assertEquals(100, insets.getSystemWindowInsets().bottom);
+        assertEquals(300, insets.getInsets(ime()).bottom);
     }
 
     // TODO: Move this to CTS once API made public
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
similarity index 78%
rename from services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java
rename to core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index b9b6d55..e23c51e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.accessibility;
+package android.view.accessibility;
 
 import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertSame;
@@ -29,16 +29,17 @@
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Instrumentation;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
 import android.os.UserHandle;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.IAccessibilityManager;
-import android.view.accessibility.IAccessibilityManagerClient;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.IntPair;
+import com.android.server.accessibility.test.MessageCapturingHandler;
 
 import org.junit.After;
 import org.junit.Before;
@@ -57,6 +58,16 @@
 public class AccessibilityManagerTest {
     private static final boolean WITH_A11Y_ENABLED = true;
     private static final boolean WITH_A11Y_DISABLED = false;
+    private static final String LABEL = "label";
+    private static final String INTENT_ACTION = "TESTACTION";
+    private static final String DESCRIPTION = "description";
+    private static final PendingIntent TEST_PENDING_INTENT = PendingIntent.getBroadcast(
+            InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION), 0);
+    private static final RemoteAction TEST_ACTION = new RemoteAction(
+            Icon.createWithContentUri("content://test"),
+            LABEL,
+            DESCRIPTION,
+            TEST_PENDING_INTENT);
 
     @Mock private IAccessibilityManager mMockService;
     private MessageCapturingHandler mHandler;
@@ -122,6 +133,29 @@
     }
 
     @Test
+    public void testRegisterSystemAction() throws Exception {
+        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+        RemoteAction action = new RemoteAction(
+                Icon.createWithContentUri("content://test"),
+                LABEL,
+                DESCRIPTION,
+                TEST_PENDING_INTENT);
+        final int actionId = 0;
+        manager.registerSystemAction(TEST_ACTION, actionId);
+
+        verify(mMockService).registerSystemAction(TEST_ACTION, actionId);
+    }
+
+    @Test
+    public void testUnregisterSystemAction() throws Exception {
+        AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+        final int actionId = 0;
+        manager.unregisterSystemAction(actionId);
+
+        verify(mMockService).unregisterSystemAction(actionId);
+    }
+
+    @Test
     public void testIsEnabled() throws Exception {
         // Create manager with a11y enabled
         AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 3586216a..93f4b51 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -124,6 +124,10 @@
 
     public void setSoftKeyboardCallbackEnabled(boolean enabled) {}
 
+    public boolean switchToInputMethod(String imeId) {
+        return false;
+    }
+
     public boolean isAccessibilityButtonAvailable() {
         return false;
     }
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index f497db2..89c2374 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -226,6 +226,61 @@
     }
 
     @Test
+    public void testEditor_onTouchEvent_quickTapAfterDrag() throws Throwable {
+        String text = "Hi world!";
+        onView(withId(R.id.textview)).perform(replaceText(text));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+        TextView tv = mActivity.findViewById(R.id.textview);
+        Editor editor = tv.getEditorForTesting();
+
+        // Simulate a tap-and-drag gesture.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 5f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event2Time = 1002;
+        MotionEvent event2 = moveEvent(event1Time, event2Time, 50f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
+        assertTrue(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event3Time = 1003;
+        MotionEvent event3 = moveEvent(event1Time, event3Time, 100f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
+        assertTrue(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event4Time = 2004;
+        MotionEvent event4 = upEvent(event1Time, event4Time, 100f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        // Simulate a quick tap after the drag, near the location where the drag ended.
+        long event5Time = 2005;
+        MotionEvent event5 = downEvent(event5Time, event5Time, 90f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event6Time = 2006;
+        MotionEvent event6 = upEvent(event5Time, event6Time, 90f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event6));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        // Simulate another quick tap in the same location; now selection should be triggered.
+        long event7Time = 2007;
+        MotionEvent event7 = downEvent(event7Time, event7Time, 90f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event7));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertTrue(editor.getSelectionController().isCursorBeingModified());
+    }
+
+    @Test
     public void testEditor_onTouchEvent_cursorDrag() throws Throwable {
         String text = "testEditor_onTouchEvent_cursorDrag";
         onView(withId(R.id.textview)).perform(replaceText(text));
@@ -237,29 +292,25 @@
         // Simulate a tap-and-drag gesture. This should trigger a cursor drag.
         long event1Time = 1001;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event1));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event2Time = 1002;
         MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event2));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event3Time = 1003;
-        MotionEvent event3 = moveEvent(event3Time, event3Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event3));
-        mInstrumentation.waitForIdleSync();
+        MotionEvent event3 = moveEvent(event1Time, event3Time, 120f, 30f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
         assertTrue(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event4Time = 1004;
-        MotionEvent event4 = upEvent(event3Time, event4Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event4));
-        mInstrumentation.waitForIdleSync();
+        MotionEvent event4 = upEvent(event1Time, event4Time, 120f, 30f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
     }
@@ -276,36 +327,31 @@
         // Simulate a double-tap followed by a drag. This should trigger a selection drag.
         long event1Time = 1001;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event1));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event2Time = 1002;
         MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event2));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event3Time = 1003;
         MotionEvent event3 = downEvent(event3Time, event3Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event3));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertTrue(editor.getSelectionController().isCursorBeingModified());
 
         long event4Time = 1004;
         MotionEvent event4 = moveEvent(event3Time, event4Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event4));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertTrue(editor.getSelectionController().isCursorBeingModified());
 
         long event5Time = 1005;
         MotionEvent event5 = upEvent(event3Time, event5Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event5));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
     }
diff --git a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
index 6adb1b8..215d0b8 100644
--- a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
+++ b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
@@ -120,6 +120,60 @@
     }
 
     @Test
+    public void testUpdate_doubleTap_delayAfterFirstDownEvent() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1000;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+
+        // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
+        long event2Time = 1000 + ViewConfiguration.getDoubleTapTimeout() + 1;
+        MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
+        mTouchState.update(event2, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+
+        // Generate an ACTION_DOWN event whose time is within the double-tap timeout when
+        // calculated from the last ACTION_UP event time. Even though the time between the last up
+        // and this down event is within the double-tap timeout, this should not be considered a
+        // double-tap (since the first down event had a longer delay).
+        long event3Time = event2Time + 1;
+        MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
+        mTouchState.update(event3, mConfig);
+        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false);
+    }
+
+    @Test
+    public void testUpdate_quickTapAfterDrag() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1000;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+
+        // Simulate an ACTION_MOVE event.
+        long event2Time = 1001;
+        MotionEvent event2 = moveEvent(event1Time, event2Time, 200f, 31f);
+        mTouchState.update(event2, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0, true);
+
+        // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
+        long event3Time = 5000;
+        MotionEvent event3 = upEvent(event1Time, event3Time, 200f, 31f);
+        mTouchState.update(event3, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 200f, 31f, false);
+
+        // Generate an ACTION_DOWN event whose time is within the double-tap timeout when
+        // calculated from the last ACTION_UP event time. Even though the time between the last up
+        // and this down event is within the double-tap timeout, this should not be considered a
+        // double-tap (since the first down event had a longer delay).
+        long event4Time = event3Time + 1;
+        MotionEvent event4 = downEvent(event4Time, event4Time, 200f, 31f);
+        mTouchState.update(event4, mConfig);
+        assertSingleTap(mTouchState, 200f, 31f, 200f, 31f, false);
+    }
+
+    @Test
     public void testUpdate_tripleClick_mouse() throws Exception {
         // Simulate an ACTION_DOWN event.
         long event1Time = 1000;
diff --git a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
index ffc925f..f108eb8 100644
--- a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
+++ b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
@@ -121,7 +121,12 @@
         AndroidFuture future2 = AndroidFuture.CREATOR.createFromParcel(parcel);
         ExecutionException executionException =
                 expectThrows(ExecutionException.class, future2::get);
-        assertThat(executionException.getCause()).isInstanceOf(UnsupportedOperationException.class);
+
+        Throwable cause = executionException.getCause();
+        String msg = cause.getMessage();
+        assertThat(cause).isInstanceOf(UnsupportedOperationException.class);
+        assertThat(msg).contains(getClass().getName());
+        assertThat(msg).contains("testWriteToParcel_Exception");
     }
 
     @Test
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 66d84aa..9018320 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -79,138 +79,6 @@
 
     @Test
     @UiThreadTest
-    public void testWindowVisibilityChange_OnCreate() throws Exception {
-        try (ClientMockSession clientSession = new ClientMockSession()) {
-            ActivityClientRecord r = clientSession.stubActivityRecord();
-
-            clientSession.launchActivity(r);
-            assertEquals(ON_CREATE, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, true);
-            assertEquals(ON_CREATE, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, false);
-            assertEquals(ON_CREATE, r.getLifecycleState());
-        }
-    }
-
-    @Test
-    @UiThreadTest
-    public void testWindowVisibilityChange_OnCreate_Finished() throws Exception {
-        try (ClientMockSession clientSession = new ClientMockSession()) {
-            ActivityClientRecord r = clientSession.stubActivityRecord();
-
-            Activity activity = clientSession.launchActivity(r);
-            activity.finish();
-            assertEquals(ON_CREATE, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, true);
-            assertEquals(ON_CREATE, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, false);
-            assertEquals(ON_CREATE, r.getLifecycleState());
-        }
-    }
-
-    @Test
-    @UiThreadTest
-    public void testWindowVisibilityChange_OnStart() throws Exception {
-        try (ClientMockSession clientSession = new ClientMockSession()) {
-            ActivityClientRecord r = clientSession.stubActivityRecord();
-
-            clientSession.launchActivity(r);
-            clientSession.startActivity(r);
-            assertEquals(ON_START, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, false);
-            assertEquals(ON_STOP, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, true);
-            assertEquals(ON_START, r.getLifecycleState());
-        }
-    }
-
-    @Test
-    @UiThreadTest
-    public void testWindowVisibilityChange_OnStart_Finished() throws Exception {
-        try (ClientMockSession clientSession = new ClientMockSession()) {
-            ActivityClientRecord r = clientSession.stubActivityRecord();
-
-            Activity activity = clientSession.launchActivity(r);
-            clientSession.startActivity(r);
-            activity.finish();
-            assertEquals(ON_START, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, false);
-            assertEquals(ON_STOP, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, true);
-            assertEquals(ON_START, r.getLifecycleState());
-        }
-    }
-
-    @Test
-    @UiThreadTest
-    public void testWindowVisibilityChange_OnResume() throws Exception {
-        try (ClientMockSession clientSession = new ClientMockSession()) {
-            ActivityClientRecord r = clientSession.stubActivityRecord();
-
-            clientSession.launchActivity(r);
-            clientSession.startActivity(r);
-            clientSession.resumeActivity(r);
-            assertEquals(ON_RESUME, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, false);
-            assertEquals(ON_STOP, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, true);
-            assertEquals(ON_START, r.getLifecycleState());
-        }
-    }
-
-    @Test
-    @UiThreadTest
-    public void testWindowVisibilityChange_OnPause() throws Exception {
-        try (ClientMockSession clientSession = new ClientMockSession()) {
-            ActivityClientRecord r = clientSession.stubActivityRecord();
-
-            clientSession.launchActivity(r);
-            clientSession.startActivity(r);
-            clientSession.resumeActivity(r);
-            clientSession.pauseActivity(r);
-            assertEquals(ON_PAUSE, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, false);
-            assertEquals(ON_STOP, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, true);
-            assertEquals(ON_START, r.getLifecycleState());
-        }
-    }
-
-    @Test
-    @UiThreadTest
-    public void testWindowVisibilityChange_OnStop() throws Exception {
-        try (ClientMockSession clientSession = new ClientMockSession()) {
-            ActivityClientRecord r = clientSession.stubActivityRecord();
-
-            clientSession.launchActivity(r);
-            clientSession.startActivity(r);
-            clientSession.resumeActivity(r);
-            clientSession.pauseActivity(r);
-            clientSession.stopActivity(r);
-            assertEquals(ON_STOP, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, true);
-            assertEquals(ON_START, r.getLifecycleState());
-
-            clientSession.changeVisibility(r, false);
-            assertEquals(ON_STOP, r.getLifecycleState());
-        }
-    }
-
-    @Test
-    @UiThreadTest
     public void testLifecycleAfterFinished_OnCreate() throws Exception {
         try (ClientMockSession clientSession = new ClientMockSession()) {
             ActivityClientRecord r = clientSession.stubActivityRecord();
@@ -308,7 +176,7 @@
         }
 
         private void startActivity(ActivityClientRecord r) {
-            mThread.handleStartActivity(r, null /* pendingActions */);
+            mThread.handleStartActivity(r.token, null /* pendingActions */);
         }
 
         private void resumeActivity(ActivityClientRecord r) {
@@ -323,7 +191,7 @@
         }
 
         private void stopActivity(ActivityClientRecord r) {
-            mThread.handleStopActivity(r.token, false /* show */, 0 /* configChanges */,
+            mThread.handleStopActivity(r.token, 0 /* configChanges */,
                     new PendingTransactionActions(), false /* finalStateRequest */, "test");
         }
 
@@ -332,10 +200,6 @@
                     false /* getNonConfigInstance */, "test");
         }
 
-        private void changeVisibility(ActivityClientRecord r, boolean show) {
-            mThread.handleWindowVisibility(r.token, show);
-        }
-
         private ActivityClientRecord stubActivityRecord() {
             ComponentName component = new ComponentName(
                     InstrumentationRegistry.getInstrumentation().getContext(), TestActivity.class);
diff --git a/core/tests/overlaytests/remount/TEST_MAPPING b/core/tests/overlaytests/remount/TEST_MAPPING
new file mode 100644
index 0000000..54dd431
--- /dev/null
+++ b/core/tests/overlaytests/remount/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name" : "OverlayRemountedTest"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/Android.bp b/core/tests/overlaytests/remount/host/Android.bp
new file mode 100644
index 0000000..3825c55
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_host {
+    name: "OverlayRemountedTest",
+    srcs: ["src/**/*.java"],
+    libs: [
+        "tradefed",
+        "junit",
+    ],
+    test_suites: ["general-tests"],
+    java_resources: [
+        ":OverlayRemountedTest_SharedLibrary",
+        ":OverlayRemountedTest_SharedLibraryOverlay",
+        ":OverlayRemountedTest_Target",
+    ],
+}
diff --git a/core/tests/overlaytests/remount/host/AndroidTest.xml b/core/tests/overlaytests/remount/host/AndroidTest.xml
new file mode 100644
index 0000000..11eadf1a
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/AndroidTest.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.
+  -->
+
+<configuration description="Test module config for OverlayRemountedTest">
+    <option name="test-tag" value="OverlayRemountedTest" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="remount" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.HostTest">
+        <option name="jar" value="OverlayRemountedTest.jar" />
+    </test>
+</configuration>
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java
new file mode 100644
index 0000000..84af187
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.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 com.android.overlaytest.remounted;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class OverlayHostTest extends BaseHostJUnit4Test {
+    private static final long TIME_OUT_MS = 30000;
+    private static final String RES_INSTRUMENTATION_ARG = "res";
+    private static final String OVERLAY_INSTRUMENTATION_ARG = "overlays";
+    private static final String RESOURCES_TYPE_SUFFIX = "_type";
+    private static final String RESOURCES_DATA_SUFFIX = "_data";
+
+    public final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+    public final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder, this::getDevice);
+
+    @Rule
+    public final RuleChain ruleChain = RuleChain.outerRule(mTemporaryFolder).around(mPreparer);
+    private Map<String, String> mLastResults;
+
+    /**
+     * Retrieves the values of the resources in the test package. The test package must use the
+     * {@link com.android.overlaytest.remounted.target.ResourceRetrievalRunner} instrumentation.
+     **/
+    void retrieveResource(String testPackageName, List<String> requiredOverlayPaths,
+            String... resourceNames) throws DeviceNotAvailableException {
+        final HashMap<String, String> args = new HashMap<>();
+        if (!requiredOverlayPaths.isEmpty()) {
+            // Enclose the require overlay paths in quotes so the arguments will be string arguments
+            // rather than file arguments.
+            args.put(OVERLAY_INSTRUMENTATION_ARG,
+                    String.format("\"%s\"", String.join(" ", requiredOverlayPaths)));
+        }
+
+        if (resourceNames.length == 0) {
+            throw new IllegalArgumentException("Must specify at least one resource to retrieve.");
+        }
+
+        // Pass the names of the resources to retrieve into the test as one string.
+        args.put(RES_INSTRUMENTATION_ARG,
+                String.format("\"%s\"", String.join(" ", resourceNames)));
+
+        runDeviceTests(getDevice(), null, testPackageName, null, null, null, TIME_OUT_MS,
+                TIME_OUT_MS, TIME_OUT_MS, false, false, args);
+
+        // Retrieve the results of the most recently run test.
+        mLastResults = (getLastDeviceRunResults().getRunMetrics() == mLastResults) ? null :
+                getLastDeviceRunResults().getRunMetrics();
+    }
+
+    /** Returns the base resource directories of the specified packages. */
+    List<String> getPackagePaths(String... packageNames)
+            throws DeviceNotAvailableException {
+        final ArrayList<String> paths = new ArrayList<>();
+        for (String packageName : packageNames) {
+            // Use the package manager shell command to find the path of the package.
+            final String result = getDevice().executeShellCommand(
+                    String.format("pm dump %s | grep \"resourcePath=\"", packageName));
+            assertNotNull("Failed to find path for package " + packageName, result);
+            int splitIndex = result.indexOf('=');
+            assertTrue(splitIndex >= 0);
+            paths.add(result.substring(splitIndex + 1).trim());
+        }
+        return paths;
+    }
+
+    /** Builds the full name of a resource in the form package:type/entry. */
+    String resourceName(String pkg, String type, String entry) {
+        return String.format("%s:%s/%s", pkg, type, entry);
+    }
+
+    /**
+     * Asserts that the type and data of a a previously retrieved is the same as expected.
+     * @param resourceName the full name of the resource in the form package:type/entry
+     * @param type the expected {@link android.util.TypedValue} type of the resource
+     * @param data the expected value of the resource when coerced to a string using
+     *             {@link android.util.TypedValue#coerceToString()}
+     **/
+    void assertResource(String resourceName, int type, String data) {
+        assertNotNull("Failed to get test results", mLastResults);
+        assertNotEquals("No resource values were retrieved", mLastResults.size(), 0);
+        assertEquals("" + type, mLastResults.get(resourceName + RESOURCES_TYPE_SUFFIX));
+        assertEquals("" + data, mLastResults.get(resourceName + RESOURCES_DATA_SUFFIX));
+    }
+}
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
new file mode 100644
index 0000000..4939e16
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest.remounted;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class OverlaySharedLibraryTest extends OverlayHostTest {
+    private static final String TARGET_APK = "OverlayRemountedTest_Target.apk";
+    private static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target";
+    private static final String SHARED_LIBRARY_APK =
+            "OverlayRemountedTest_SharedLibrary.apk";
+    private static final String SHARED_LIBRARY_PACKAGE =
+            "com.android.overlaytest.remounted.shared_library";
+    private static final String SHARED_LIBRARY_OVERLAY_APK =
+            "OverlayRemountedTest_SharedLibraryOverlay.apk";
+    private static final String SHARED_LIBRARY_OVERLAY_PACKAGE =
+            "com.android.overlaytest.remounted.shared_library.overlay";
+
+    @Test
+    public void testSharedLibrary() throws Exception {
+        final String targetResource = resourceName(TARGET_PACKAGE, "bool",
+                "uses_shared_library_overlaid");
+        final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
+                "shared_library_overlaid");
+
+        mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+                .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
+                .reboot()
+                .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, false)
+                .installResourceApk(TARGET_APK, TARGET_PACKAGE);
+
+        // The shared library resource is not currently overlaid.
+        retrieveResource(Collections.emptyList(), targetResource, libraryResource);
+        assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "false");
+        assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "false");
+
+        // Overlay the shared library resource.
+        mPreparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true);
+        retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource,
+                libraryResource);
+        assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+        assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+    }
+
+    @Test
+    public void testSharedLibraryPreEnabled() throws Exception {
+        final String targetResource = resourceName(TARGET_PACKAGE, "bool",
+                "uses_shared_library_overlaid");
+        final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
+                "shared_library_overlaid");
+
+        mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+                .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
+                .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true)
+                .reboot()
+                .installResourceApk(TARGET_APK, TARGET_PACKAGE);
+
+        retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource,
+                libraryResource);
+        assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+        assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+    }
+
+    private void retrieveResource(List<String> requiredOverlayPaths, String... resourceNames)
+            throws DeviceNotAvailableException {
+        retrieveResource(TARGET_PACKAGE, requiredOverlayPaths, resourceNames);
+    }
+}
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java
new file mode 100644
index 0000000..7028f2f
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest.remounted;
+
+import static org.junit.Assert.assertTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+import org.junit.Assert;
+import org.junit.rules.ExternalResource;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeoutException;
+
+class SystemPreparer extends ExternalResource {
+    private static final long REBOOT_SLEEP_MS = 30000;
+    private static final long OVERLAY_ENABLE_TIMEOUT_MS = 20000;
+
+    // The paths of the files pushed onto the device through this rule.
+    private ArrayList<String> mPushedFiles = new ArrayList<>();
+
+    // The package names of packages installed through this rule.
+    private ArrayList<String> mInstalledPackages = new ArrayList<>();
+
+    private final TemporaryFolder mHostTempFolder;
+    private final DeviceProvider mDeviceProvider;
+
+    SystemPreparer(TemporaryFolder hostTempFolder, DeviceProvider deviceProvider) {
+        mHostTempFolder = hostTempFolder;
+        mDeviceProvider = deviceProvider;
+    }
+
+    /** Copies a file within the host test jar to a path on device. */
+    SystemPreparer pushResourceFile(String resourcePath,
+            String outputPath) throws DeviceNotAvailableException, IOException {
+        final ITestDevice device = mDeviceProvider.getDevice();
+        assertTrue(device.pushFile(copyResourceToTemp(resourcePath), outputPath));
+        mPushedFiles.add(outputPath);
+        return this;
+    }
+
+    /** Installs an APK within the host test jar onto the device. */
+    SystemPreparer installResourceApk(String resourcePath, String packageName)
+            throws DeviceNotAvailableException, IOException {
+        final ITestDevice device = mDeviceProvider.getDevice();
+        final File tmpFile = copyResourceToTemp(resourcePath);
+        final String result = device.installPackage(tmpFile, true);
+        Assert.assertNull(result);
+        mInstalledPackages.add(packageName);
+        return this;
+    }
+
+    /** Sets the enable state of an overlay pacakage. */
+    SystemPreparer setOverlayEnabled(String packageName, boolean enabled)
+            throws ExecutionException, TimeoutException {
+        final ITestDevice device = mDeviceProvider.getDevice();
+
+        // Wait for the overlay to change its enabled state.
+        final FutureTask<Boolean> enabledListener = new FutureTask<>(() -> {
+            while (true) {
+                device.executeShellCommand(String.format("cmd overlay %s %s",
+                        enabled ? "enable" : "disable", packageName));
+
+                final String pattern = (enabled ? "[x]" : "[ ]") + " " + packageName;
+                if (device.executeShellCommand("cmd overlay list").contains(pattern)) {
+                    return true;
+                }
+            }
+        });
+
+        final Executor executor = (cmd) -> new Thread(cmd).start();
+        executor.execute(enabledListener);
+        try {
+            enabledListener.get(OVERLAY_ENABLE_TIMEOUT_MS, MILLISECONDS);
+        } catch (InterruptedException ignored) {
+        }
+
+        return this;
+    }
+
+    /** Restarts the device and waits until after boot is completed. */
+    SystemPreparer reboot() throws DeviceNotAvailableException {
+        final ITestDevice device = mDeviceProvider.getDevice();
+        device.executeShellCommand("stop");
+        device.executeShellCommand("start");
+        try {
+            // Sleep until the device is ready for test execution.
+            Thread.sleep(REBOOT_SLEEP_MS);
+        } catch (InterruptedException ignored) {
+        }
+
+        return this;
+    }
+
+    /** Copies a file within the host test jar to a temporary file on the host machine. */
+    private File copyResourceToTemp(String resourcePath) throws IOException {
+        final File tempFile = mHostTempFolder.newFile(resourcePath);
+        final ClassLoader classLoader = getClass().getClassLoader();
+        try (InputStream assetIs = classLoader.getResource(resourcePath).openStream();
+             FileOutputStream assetOs = new FileOutputStream(tempFile)) {
+            if (assetIs == null) {
+                throw new IllegalStateException("Failed to find resource " + resourcePath);
+            }
+
+            int b;
+            while ((b = assetIs.read()) >= 0) {
+                assetOs.write(b);
+            }
+        }
+
+        return tempFile;
+    }
+
+    /** Removes installed packages and files that were pushed to the device. */
+    @Override
+    protected void after() {
+        final ITestDevice device = mDeviceProvider.getDevice();
+        try {
+            for (final String file : mPushedFiles) {
+                device.deleteFile(file);
+            }
+            for (final String packageName : mInstalledPackages) {
+                device.uninstallPackage(packageName);
+            }
+        } catch (DeviceNotAvailableException e) {
+            Assert.fail(e.toString());
+        }
+    }
+
+    interface DeviceProvider {
+        ITestDevice getDevice();
+    }
+}
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp
new file mode 100644
index 0000000..ffb0572
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp
@@ -0,0 +1,19 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "OverlayRemountedTest_SharedLibrary",
+    sdk_version: "current",
+    aaptflags: ["--shared-lib"],
+}
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml
new file mode 100644
index 0000000..06e3f6a
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.overlaytest.remounted.shared_library">
+    <application>
+        <library android:name="com.android.overlaytest.remounted.shared_library" />
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml
new file mode 100644
index 0000000..1b06f6d
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <overlayable name="TestResources">
+        <policy type="public">
+            <item type="bool" name="shared_library_overlaid" />
+        </policy>
+    </overlayable>
+</resources>
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml
new file mode 100644
index 0000000..5b9db16
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <public type="bool" name="shared_library_overlaid" id="0x00050001"/>
+</resources>
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml
new file mode 100644
index 0000000..2dc47a7
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <bool name="shared_library_overlaid">false</bool>
+</resources>
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp
new file mode 100644
index 0000000..0d29aec
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp
@@ -0,0 +1,18 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "OverlayRemountedTest_SharedLibraryOverlay",
+    sdk_version: "current",
+}
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..53a4e61
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.overlaytest.remounted.shared_library.overlay">
+    <application android:hasCode="false" />
+    <overlay android:targetPackage="com.android.overlaytest.remounted.shared_library"
+             android:targetName="TestResources" />
+</manifest>
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml
new file mode 100644
index 0000000..f66448a
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <bool name="shared_library_overlaid">true</bool>
+</resources>
diff --git a/core/tests/overlaytests/remount/target/Android.bp b/core/tests/overlaytests/remount/target/Android.bp
new file mode 100644
index 0000000..83f9f28
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/Android.bp
@@ -0,0 +1,20 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "OverlayRemountedTest_Target",
+    srcs: ["src/**/*.java"],
+    sdk_version: "test_current",
+    libs: ["OverlayRemountedTest_SharedLibrary"],
+}
diff --git a/core/tests/overlaytests/remount/target/AndroidManifest.xml b/core/tests/overlaytests/remount/target/AndroidManifest.xml
new file mode 100644
index 0000000..32fec43
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.overlaytest.remounted.target">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="com.android.overlaytest.remounted.shared_library"
+                      android:required="true" />
+    </application>
+
+    <instrumentation android:name="com.android.overlaytest.remounted.target.ResourceRetrievalRunner"
+                     android:targetPackage="com.android.overlaytest.remounted.target"
+                     android:label="Remounted system RRO tests" />
+</manifest>
diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/target/res/values/values.xml
new file mode 100644
index 0000000..b5f444a
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/res/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library">
+    <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool>
+</resources>
diff --git a/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java b/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java
new file mode 100644
index 0000000..2e4c211
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest.remounted.target;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.TypedValue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An {@link Instrumentation} that retrieves the value of specified resources within the
+ * application.
+ **/
+public class ResourceRetrievalRunner extends Instrumentation {
+    private static final String TAG = ResourceRetrievalRunner.class.getSimpleName();
+
+    // A list of whitespace separated resource names of which to retrieve the resource values.
+    private static final String RESOURCE_LIST_TAG = "res";
+
+    // A list of whitespace separated overlay package paths that must be present before retrieving
+    // resource values.
+    private static final String REQUIRED_OVERLAYS_LIST_TAG = "overlays";
+
+    // The suffixes of the keys returned from the instrumentation. To retrieve the type of a
+    // resource looked up with the instrumentation, append the {@link #RESOURCES_TYPE_SUFFIX} suffix
+    // to the end of the name of the resource. For the value of a resource, use
+    // {@link #RESOURCES_DATA_SUFFIX} instead.
+    private static final String RESOURCES_TYPE_SUFFIX = "_type";
+    private static final String RESOURCES_DATA_SUFFIX = "_data";
+
+    // The amount of time in seconds to wait for the overlays to be present in the AssetManager.
+    private static final int OVERLAY_PATH_TIMEOUT = 60;
+
+    private final ArrayList<String> mResourceNames = new ArrayList<>();
+    private final ArrayList<String> mOverlayPaths = new ArrayList<>();
+    private final Bundle mResult = new Bundle();
+
+    /**
+     * Receives the instrumentation arguments and runs the resource retrieval.
+     * The entry with key {@link #RESOURCE_LIST_TAG} in the {@link Bundle} arguments is a
+     * whitespace separated string of resource names of which to retrieve the resource values.
+     * The entry with key {@link #REQUIRED_OVERLAYS_LIST_TAG} in the {@link Bundle} arguments is a
+     * whitespace separated string of overlay package paths prefixes that must be present before
+     * retrieving the resource values.
+     */
+    @Override
+    public void onCreate(Bundle arguments) {
+        super.onCreate(arguments);
+        mResourceNames.addAll(Arrays.asList(arguments.getString(RESOURCE_LIST_TAG).split(" ")));
+        if (arguments.containsKey(REQUIRED_OVERLAYS_LIST_TAG)) {
+            mOverlayPaths.addAll(Arrays.asList(
+                    arguments.getString(REQUIRED_OVERLAYS_LIST_TAG).split(" ")));
+        }
+        start();
+    }
+
+    @Override
+    public void onStart() {
+        final Resources res = getContext().getResources();
+        res.getAssets().setResourceResolutionLoggingEnabled(true);
+
+        if (!mOverlayPaths.isEmpty()) {
+            Log.d(TAG, String.format("Waiting for overlay paths [%s]",
+                    String.join(",", mOverlayPaths)));
+
+            // Wait for all required overlays to be present in the AssetManager.
+            final FutureTask<Boolean> overlayListener = new FutureTask<>(() -> {
+                while (!mOverlayPaths.isEmpty()) {
+                    final String[] apkPaths = res.getAssets().getApkPaths();
+                    for (String path : apkPaths) {
+                        for (String overlayPath : mOverlayPaths) {
+                            if (path.startsWith(overlayPath)) {
+                                mOverlayPaths.remove(overlayPath);
+                                break;
+                            }
+                        }
+                    }
+                }
+                return true;
+            });
+
+            try {
+                final Executor executor = (t) -> new Thread(t).start();
+                executor.execute(overlayListener);
+                overlayListener.get(OVERLAY_PATH_TIMEOUT, TimeUnit.SECONDS);
+            } catch (Exception e) {
+                Log.e(TAG, String.format("Failed to wait for required overlays [%s]",
+                        String.join(",", mOverlayPaths)), e);
+                finish(Activity.RESULT_CANCELED, mResult);
+            }
+        }
+
+        // Retrieve the values for each resource passed in.
+        final TypedValue typedValue = new TypedValue();
+        for (final String resourceName : mResourceNames) {
+            try {
+                final int resId = res.getIdentifier(resourceName, null, null);
+                res.getValue(resId, typedValue, true);
+                Log.d(TAG, String.format("Resolution for 0x%s: %s", Integer.toHexString(resId),
+                        res.getAssets().getLastResourceResolution()));
+            } catch (Resources.NotFoundException e) {
+                Log.e(TAG, "Failed to retrieve value for resource " + resourceName, e);
+                finish(Activity.RESULT_CANCELED, mResult);
+            }
+
+            putValue(resourceName, typedValue);
+        }
+
+        finish(Activity.RESULT_OK, mResult);
+    }
+
+    private void putValue(String resourceName, TypedValue value) {
+        mResult.putInt(resourceName + RESOURCES_TYPE_SUFFIX, value.type);
+        final CharSequence textValue = value.coerceToString();
+        mResult.putString(resourceName + RESOURCES_DATA_SUFFIX,
+                textValue == null ? "null" : textValue.toString());
+    }
+}
diff --git a/core/xsd/permission.xsd b/core/xsd/permission.xsd
index cc01a31..5435047 100644
--- a/core/xsd/permission.xsd
+++ b/core/xsd/permission.xsd
@@ -46,6 +46,7 @@
                 <xs:element name="hidden-api-whitelisted-app" type="hidden-api-whitelisted-app"/>
                 <xs:element name="allow-association" type="allow-association"/>
                 <xs:element name="bugreport-whitelisted" type="bugreport-whitelisted"/>
+                <xs:element name="app-data-isolation-whitelisted-app" type="app-data-isolation-whitelisted-app"/>
             </xs:choice>
         </xs:complexType>
     </xs:element>
@@ -161,6 +162,9 @@
         <xs:attribute name="target" type="xs:string"/>
         <xs:attribute name="allowed" type="xs:string"/>
     </xs:complexType>
+    <xs:complexType name="app-data-isolation-whitelisted-app">
+        <xs:attribute name="package" type="xs:string"/>
+    </xs:complexType>
     <xs:complexType name="bugreport-whitelisted">
         <xs:attribute name="package" type="xs:string"/>
     </xs:complexType>
diff --git a/core/xsd/schema/current.txt b/core/xsd/schema/current.txt
index 771c1df..c36c422 100644
--- a/core/xsd/schema/current.txt
+++ b/core/xsd/schema/current.txt
@@ -45,6 +45,12 @@
     method public void set_package(String);
   }
 
+  public class AppDataIsolationWhitelistedApp {
+    ctor public AppDataIsolationWhitelistedApp();
+    method public String get_package();
+    method public void set_package(String);
+  }
+
   public class AppLink {
     ctor public AppLink();
     method public String get_package();
@@ -160,6 +166,7 @@
     method public java.util.List<com.android.xml.permission.configfile.AllowInPowerSaveExceptIdle> getAllowInPowerSaveExceptIdle_optional();
     method public java.util.List<com.android.xml.permission.configfile.AllowInPowerSave> getAllowInPowerSave_optional();
     method public java.util.List<com.android.xml.permission.configfile.AllowUnthrottledLocation> getAllowUnthrottledLocation_optional();
+    method public java.util.List<com.android.xml.permission.configfile.AppDataIsolationWhitelistedApp> getAppDataIsolationWhitelistedApp_optional();
     method public java.util.List<com.android.xml.permission.configfile.AppLink> getAppLink_optional();
     method public java.util.List<com.android.xml.permission.configfile.AssignPermission> getAssignPermission_optional();
     method public java.util.List<com.android.xml.permission.configfile.BackupTransportWhitelistedService> getBackupTransportWhitelistedService_optional();
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index ea66ee3..70d4678 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -1 +1 @@
-per-file privapp-permissions-platform.xml = hackbod@android.com, jsharkey@android.com, svetoslavganov@google.com, toddke@google.com, yamasani@google.com, cbrubaker@google.com, jeffv@google.com, moltmann@google.com
+per-file privapp-permissions-platform.xml = hackbod@android.com, jsharkey@android.com, svetoslavganov@google.com, toddke@google.com, yamasani@google.com, cbrubaker@google.com, jeffv@google.com, moltmann@google.com, lorenzo@google.com
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index eb1d1ab..9930ea2 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -243,6 +243,7 @@
         <permission name="android.permission.MANAGE_USB"/>
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
+        <permission name="android.permission.TETHER_PRIVILEGED"/>
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
     </privapp-permissions>
 
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 8765719..3f2f349 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -44,6 +44,7 @@
         "AttributeResolution.cpp",
         "ChunkIterator.cpp",
         "ConfigDescription.cpp",
+        "DynamicLibManager.cpp",
         "Idmap.cpp",
         "LoadedArsc.cpp",
         "Locale.cpp",
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index ca4143f..8cfd2d8 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -25,6 +25,7 @@
 
 #include "android-base/logging.h"
 #include "android-base/stringprintf.h"
+#include "androidfw/DynamicLibManager.h"
 #include "androidfw/ResourceUtils.h"
 #include "androidfw/Util.h"
 #include "utils/ByteOrder.h"
@@ -66,7 +67,12 @@
   StringPoolRef entry_string_ref;
 };
 
-AssetManager2::AssetManager2() {
+AssetManager2::AssetManager2() : dynamic_lib_manager_(std::make_unique<DynamicLibManager>()) {
+  memset(&configuration_, 0, sizeof(configuration_));
+}
+
+AssetManager2::AssetManager2(DynamicLibManager* dynamic_lib_manager)
+    : dynamic_lib_manager_(dynamic_lib_manager) {
   memset(&configuration_, 0, sizeof(configuration_));
 }
 
@@ -85,25 +91,45 @@
   package_groups_.clear();
   package_ids_.fill(0xff);
 
-  // A mapping from apk assets path to the runtime package id of its first loaded package.
+  // Overlay resources are not directly referenced by an application so their resource ids
+  // can change throughout the application's lifetime. Assign overlay package ids last.
+  std::vector<const ApkAssets*> sorted_apk_assets(apk_assets_);
+  std::stable_partition(sorted_apk_assets.begin(), sorted_apk_assets.end(), [](const ApkAssets* a) {
+    return !a->IsOverlay();
+  });
+
   std::unordered_map<std::string, uint8_t> apk_assets_package_ids;
+  std::unordered_map<std::string, uint8_t> package_name_package_ids;
 
-  // 0x01 is reserved for the android package.
-  int next_package_id = 0x02;
-  const size_t apk_assets_count = apk_assets_.size();
-  for (size_t i = 0; i < apk_assets_count; i++) {
-    const ApkAssets* apk_assets = apk_assets_[i];
-    const LoadedArsc* loaded_arsc = apk_assets->GetLoadedArsc();
-
-    for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {
-      // Get the package ID or assign one if a shared library.
-      int package_id;
-      if (package->IsDynamic()) {
-        package_id = next_package_id++;
-      } else {
-        package_id = package->GetPackageId();
+  // Assign stable package ids to application packages.
+  uint8_t next_available_package_id = 0U;
+  for (const auto& apk_assets : sorted_apk_assets) {
+    for (const auto& package : apk_assets->GetLoadedArsc()->GetPackages()) {
+      uint8_t package_id = package->GetPackageId();
+      if (package->IsOverlay()) {
+        package_id = GetDynamicLibManager()->FindUnassignedId(next_available_package_id);
+        next_available_package_id = package_id + 1;
+      } else if (package->IsDynamic()) {
+        package_id = GetDynamicLibManager()->GetAssignedId(package->GetPackageName());
       }
 
+      // Map the path of the apk assets to the package id of its first loaded package.
+      apk_assets_package_ids[apk_assets->GetPath()] = package_id;
+
+      // Map the package name of the package to the first loaded package with that package id.
+      package_name_package_ids[package->GetPackageName()] = package_id;
+    }
+  }
+
+  const int apk_assets_count = apk_assets_.size();
+  for (int i = 0; i < apk_assets_count; i++) {
+    const auto& apk_assets = apk_assets_[i];
+    for (const auto& package : apk_assets->GetLoadedArsc()->GetPackages()) {
+      const auto package_id_entry = package_name_package_ids.find(package->GetPackageName());
+      CHECK(package_id_entry != package_name_package_ids.end())
+          << "no package id assgined to package " << package->GetPackageName();
+      const uint8_t package_id = package_id_entry->second;
+
       // Add the mapping for package ID to index if not present.
       uint8_t idx = package_ids_[package_id];
       if (idx == 0xff) {
@@ -115,7 +141,10 @@
           // to take effect.
           const auto& loaded_idmap = apk_assets->GetLoadedIdmap();
           auto target_package_iter = apk_assets_package_ids.find(loaded_idmap->TargetApkPath());
-          if (target_package_iter != apk_assets_package_ids.end()) {
+          if (target_package_iter == apk_assets_package_ids.end()) {
+             LOG(INFO) << "failed to find target package for overlay "
+                       << loaded_idmap->OverlayApkPath();
+          } else {
             const uint8_t target_package_id = target_package_iter->second;
             const uint8_t target_idx = package_ids_[target_package_id];
             CHECK(target_idx != 0xff) << "overlay added to apk_assets_package_ids but does not"
@@ -123,7 +152,7 @@
 
             PackageGroup& target_package_group = package_groups_[target_idx];
 
-            // Create a special dynamic reference table for the overlay to rewite references to
+            // Create a special dynamic reference table for the overlay to rewrite references to
             // overlay resources as references to the target resources they overlay.
             auto overlay_table = std::make_shared<OverlayDynamicRefTable>(
                 loaded_idmap->GetOverlayDynamicRefTable(target_package_id));
@@ -153,8 +182,6 @@
         package_group->dynamic_ref_table->mEntries.replaceValueFor(
             package_name, static_cast<uint8_t>(entry.package_id));
       }
-
-      apk_assets_package_ids.insert(std::make_pair(apk_assets->GetPath(), package_id));
     }
   }
 
@@ -567,7 +594,7 @@
       if (resource_resolution_logging_enabled_) {
         last_resolution_.steps.push_back(
             Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result.config.toString(),
-                             &package_group.packages_[0].loaded_package_->GetPackageName()});
+                             overlay_result.package_name});
       }
     }
   }
@@ -1279,6 +1306,16 @@
   return 0;
 }
 
+DynamicLibManager* AssetManager2::GetDynamicLibManager() const {
+  auto dynamic_lib_manager =
+      std::get_if<std::unique_ptr<DynamicLibManager>>(&dynamic_lib_manager_);
+  if (dynamic_lib_manager) {
+    return (*dynamic_lib_manager).get();
+  } else {
+    return *std::get_if<DynamicLibManager*>(&dynamic_lib_manager_);
+  }
+}
+
 std::unique_ptr<Theme> AssetManager2::NewTheme() {
   return std::unique_ptr<Theme>(new Theme(this));
 }
diff --git a/libs/androidfw/DynamicLibManager.cpp b/libs/androidfw/DynamicLibManager.cpp
new file mode 100644
index 0000000..895b769
--- /dev/null
+++ b/libs/androidfw/DynamicLibManager.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "androidfw/DynamicLibManager.h"
+
+namespace android {
+
+uint8_t DynamicLibManager::GetAssignedId(const std::string& library_package_name) {
+  auto lib_entry = shared_lib_package_ids_.find(library_package_name);
+  if (lib_entry != shared_lib_package_ids_.end()) {
+    return lib_entry->second;
+  }
+
+  return shared_lib_package_ids_[library_package_name] = next_package_id_++;
+}
+
+uint8_t DynamicLibManager::FindUnassignedId(uint8_t start_package_id) {
+  return (start_package_id < next_package_id_) ? next_package_id_ : start_package_id;
+}
+
+} // namespace android
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 00cbbca..b2cec2a 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -27,6 +27,7 @@
 #include "androidfw/ApkAssets.h"
 #include "androidfw/Asset.h"
 #include "androidfw/AssetManager.h"
+#include "androidfw/DynamicLibManager.h"
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/Util.h"
 
@@ -94,6 +95,7 @@
   };
 
   AssetManager2();
+  explicit AssetManager2(DynamicLibManager* dynamic_lib_manager);
 
   // Sets/resets the underlying ApkAssets for this AssetManager. The ApkAssets
   // are not owned by the AssetManager, and must have a longer lifetime.
@@ -371,6 +373,8 @@
   // Retrieve the assigned package id of the package if loaded into this AssetManager
   uint8_t GetAssignedPackageId(const LoadedPackage* package) const;
 
+  DynamicLibManager* GetDynamicLibManager() const;
+
   // The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must
   // have a longer lifetime.
   std::vector<const ApkAssets*> apk_assets_;
@@ -389,6 +393,9 @@
   // may need to be purged.
   ResTable_config configuration_;
 
+  // Component responsible for assigning package ids to shared libraries.
+  std::variant<std::unique_ptr<DynamicLibManager>, DynamicLibManager*> dynamic_lib_manager_;
+
   // Cached set of bags. These are cached because they can inherit keys from parent bags,
   // which involves some calculation.
   std::unordered_map<uint32_t, util::unique_cptr<ResolvedBag>> cached_bags_;
diff --git a/libs/androidfw/include/androidfw/DynamicLibManager.h b/libs/androidfw/include/androidfw/DynamicLibManager.h
new file mode 100644
index 0000000..1ff7079
--- /dev/null
+++ b/libs/androidfw/include/androidfw/DynamicLibManager.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROIDFW_DYNAMICLIBMANAGER_H
+#define ANDROIDFW_DYNAMICLIBMANAGER_H
+
+#include <string>
+#include <unordered_map>
+
+#include "android-base/macros.h"
+
+namespace android {
+
+// Manages assigning resource ids for dynamic resources.
+class DynamicLibManager {
+ public:
+  DynamicLibManager() = default;
+
+  // Retrieves the assigned package id for the library.
+  uint8_t GetAssignedId(const std::string& library_package_name);
+
+  // Queries in ascending order for the first available package id that is not currently assigned to
+  // a library.
+  uint8_t FindUnassignedId(uint8_t start_package_id);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DynamicLibManager);
+
+  uint8_t next_package_id_ = 0x02;
+  std::unordered_map<std::string, uint8_t> shared_lib_package_ids_;
+};
+
+} // namespace android
+
+#endif //ANDROIDFW_DYNAMICLIBMANAGER_H
diff --git a/libs/androidfw/include/androidfw/MutexGuard.h b/libs/androidfw/include/androidfw/MutexGuard.h
index 64924f4..8891512 100644
--- a/libs/androidfw/include/androidfw/MutexGuard.h
+++ b/libs/androidfw/include/androidfw/MutexGuard.h
@@ -47,7 +47,8 @@
   static_assert(!std::is_pointer<T>::value, "T must not be a raw pointer");
 
  public:
-  explicit Guarded() : guarded_() {
+  template <typename ...Args>
+  explicit Guarded(Args&& ...args) : guarded_(std::forward<Args>(args)...) {
   }
 
   template <typename U = T>
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index b3190be..2f6f3df 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -214,6 +214,25 @@
   EXPECT_EQ(fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data);
 }
 
+TEST_F(AssetManager2Test, AssignsUnchangingPackageIdToSharedLibrary) {
+  DynamicLibManager lib_manager;
+  AssetManager2 assetmanager(&lib_manager);
+  assetmanager.SetApkAssets(
+      {lib_one_assets_.get(), lib_two_assets_.get(), libclient_assets_.get()});
+
+  AssetManager2 assetmanager2(&lib_manager);
+  assetmanager2.SetApkAssets(
+      {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+
+  uint32_t res_id = assetmanager.GetResourceId("com.android.lib_one:string/foo");
+  ASSERT_NE(0U, res_id);
+
+  uint32_t res_id_2 = assetmanager2.GetResourceId("com.android.lib_one:string/foo");
+  ASSERT_NE(0U, res_id_2);
+
+  ASSERT_EQ(res_id, res_id_2);
+}
+
 TEST_F(AssetManager2Test, GetSharedLibraryResourceName) {
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets({lib_one_assets_.get()});
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index d945fc4..a7f8cc4 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -215,6 +215,7 @@
         android: {
 
             srcs: [
+                "pipeline/skia/ATraceMemoryDump.cpp",
                 "pipeline/skia/GLFunctorDrawable.cpp",
                 "pipeline/skia/LayerDrawable.cpp",
                 "pipeline/skia/ShaderCache.cpp",
@@ -244,7 +245,6 @@
                 "DeviceInfo.cpp",
                 "FrameInfo.cpp",
                 "FrameInfoVisualizer.cpp",
-                "GpuMemoryTracker.cpp",
                 "HardwareBitmapUploader.cpp",
                 "HWUIProperties.sysprop",
                 "JankTracker.cpp",
@@ -325,7 +325,6 @@
         "tests/unit/DamageAccumulatorTests.cpp",
         "tests/unit/DeferredLayerUpdaterTests.cpp",
         "tests/unit/FatVectorTests.cpp",
-        "tests/unit/GpuMemoryTrackerTests.cpp",
         "tests/unit/GraphicsStatsServiceTests.cpp",
         "tests/unit/LayerUpdateQueueTests.cpp",
         "tests/unit/LinearAllocatorTests.cpp",
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index e53f3db..6d4a0c6 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -52,8 +52,8 @@
     DeviceInfo::get()->mMaxTextureSize = maxTextureSize;
 }
 
-void DeviceInfo::onDisplayConfigChanged() {
-    updateDisplayInfo();
+void DeviceInfo::onRefreshRateChanged(int64_t vsyncPeriod) {
+    mVsyncPeriod = vsyncPeriod;
 }
 
 void DeviceInfo::updateDisplayInfo() {
@@ -113,10 +113,11 @@
     ADisplay* primaryDisplay = mDisplays[mPhysicalDisplayIndex];
     status_t status = ADisplay_getCurrentConfig(primaryDisplay, &mCurrentConfig);
     LOG_ALWAYS_FATAL_IF(status, "Failed to get display config, error %d", status);
+
     mWidth = ADisplayConfig_getWidth(mCurrentConfig);
     mHeight = ADisplayConfig_getHeight(mCurrentConfig);
     mDensity = ADisplayConfig_getDensity(mCurrentConfig);
-    mRefreshRate = ADisplayConfig_getFps(mCurrentConfig);
+    mVsyncPeriod = static_cast<int64_t>(1000000000 / ADisplayConfig_getFps(mCurrentConfig));
     mCompositorOffset = ADisplayConfig_getCompositorOffsetNanos(mCurrentConfig);
     mAppOffset = ADisplayConfig_getAppVsyncOffsetNanos(mCurrentConfig);
 }
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index a420746..16a22f4 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -37,7 +37,7 @@
     static int32_t getWidth() { return get()->mWidth; }
     static int32_t getHeight() { return get()->mHeight; }
     static float getDensity() { return get()->mDensity; }
-    static float getRefreshRate() { return get()->mRefreshRate; }
+    static int64_t getVsyncPeriod() { return get()->mVsyncPeriod; }
     static int64_t getCompositorOffset() { return get()->mCompositorOffset; }
     static int64_t getAppOffset() { return get()->mAppOffset; }
 
@@ -47,7 +47,8 @@
     sk_sp<SkColorSpace> getWideColorSpace() const { return mWideColorSpace; }
     SkColorType getWideColorType() const { return mWideColorType; }
 
-    void onDisplayConfigChanged();
+    // This method should be called whenever the display refresh rate changes.
+    void onRefreshRateChanged(int64_t vsyncPeriod);
 
 private:
     friend class renderthread::RenderThread;
@@ -68,7 +69,7 @@
     int32_t mWidth = 1080;
     int32_t mHeight = 1920;
     float mDensity = 2.0;
-    float mRefreshRate = 60.0;
+    int64_t mVsyncPeriod = 16666666;
     int64_t mCompositorOffset = 0;
     int64_t mAppOffset = 0;
 };
diff --git a/libs/hwui/GpuMemoryTracker.cpp b/libs/hwui/GpuMemoryTracker.cpp
deleted file mode 100644
index a9a7af8..0000000
--- a/libs/hwui/GpuMemoryTracker.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "utils/StringUtils.h"
-
-#include <GpuMemoryTracker.h>
-#include <cutils/compiler.h>
-#include <utils/Trace.h>
-#include <array>
-#include <sstream>
-#include <unordered_set>
-#include <vector>
-
-namespace android {
-namespace uirenderer {
-
-pthread_t gGpuThread = 0;
-
-#define NUM_TYPES static_cast<int>(GpuObjectType::TypeCount)
-
-const char* TYPE_NAMES[] = {
-        "Texture", "OffscreenBuffer", "Layer",
-};
-
-struct TypeStats {
-    int totalSize = 0;
-    int count = 0;
-};
-
-static std::array<TypeStats, NUM_TYPES> gObjectStats;
-static std::unordered_set<GpuMemoryTracker*> gObjectSet;
-
-void GpuMemoryTracker::notifySizeChanged(int newSize) {
-    int delta = newSize - mSize;
-    mSize = newSize;
-    gObjectStats[static_cast<int>(mType)].totalSize += delta;
-}
-
-void GpuMemoryTracker::startTrackingObject() {
-    auto result = gObjectSet.insert(this);
-    LOG_ALWAYS_FATAL_IF(!result.second,
-                        "startTrackingObject() on %p failed, already being tracked!", this);
-    gObjectStats[static_cast<int>(mType)].count++;
-}
-
-void GpuMemoryTracker::stopTrackingObject() {
-    size_t removed = gObjectSet.erase(this);
-    LOG_ALWAYS_FATAL_IF(removed != 1, "stopTrackingObject removed %zd, is %p not being tracked?",
-                        removed, this);
-    gObjectStats[static_cast<int>(mType)].count--;
-}
-
-void GpuMemoryTracker::onGpuContextCreated() {
-    LOG_ALWAYS_FATAL_IF(gGpuThread != 0,
-                        "We already have a gpu thread? "
-                        "current = %lu, gpu thread = %lu",
-                        pthread_self(), gGpuThread);
-    gGpuThread = pthread_self();
-}
-
-void GpuMemoryTracker::onGpuContextDestroyed() {
-    gGpuThread = 0;
-    if (CC_UNLIKELY(gObjectSet.size() > 0)) {
-        std::stringstream os;
-        dump(os);
-        ALOGE("%s", os.str().c_str());
-        LOG_ALWAYS_FATAL("Leaked %zd GPU objects!", gObjectSet.size());
-    }
-}
-
-void GpuMemoryTracker::dump() {
-    std::stringstream strout;
-    dump(strout);
-    ALOGD("%s", strout.str().c_str());
-}
-
-void GpuMemoryTracker::dump(std::ostream& stream) {
-    for (int type = 0; type < NUM_TYPES; type++) {
-        const TypeStats& stats = gObjectStats[type];
-        stream << TYPE_NAMES[type];
-        stream << " is using " << SizePrinter{stats.totalSize};
-        stream << ", count = " << stats.count;
-        stream << std::endl;
-    }
-}
-
-int GpuMemoryTracker::getInstanceCount(GpuObjectType type) {
-    return gObjectStats[static_cast<int>(type)].count;
-}
-
-int GpuMemoryTracker::getTotalSize(GpuObjectType type) {
-    return gObjectStats[static_cast<int>(type)].totalSize;
-}
-
-void GpuMemoryTracker::onFrameCompleted() {
-    if (ATRACE_ENABLED()) {
-        char buf[128];
-        for (int type = 0; type < NUM_TYPES; type++) {
-            snprintf(buf, 128, "hwui_%s", TYPE_NAMES[type]);
-            const TypeStats& stats = gObjectStats[type];
-            ATRACE_INT(buf, stats.totalSize);
-            snprintf(buf, 128, "hwui_%s_count", TYPE_NAMES[type]);
-            ATRACE_INT(buf, stats.count);
-        }
-    }
-}
-
-}  // namespace uirenderer
-}  // namespace android;
diff --git a/libs/hwui/GpuMemoryTracker.h b/libs/hwui/GpuMemoryTracker.h
deleted file mode 100644
index de3ca99..0000000
--- a/libs/hwui/GpuMemoryTracker.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#include <pthread.h>
-#include <ostream>
-
-#include <log/log.h>
-
-namespace android {
-namespace uirenderer {
-
-extern pthread_t gGpuThread;
-
-#define ASSERT_GPU_THREAD()                                                                    \
-    LOG_ALWAYS_FATAL_IF(!pthread_equal(gGpuThread, pthread_self()),                            \
-                        "Error, %p of type %d (size=%d) used on wrong thread! cur thread %lu " \
-                        "!= gpu thread %lu",                                                   \
-                        this, static_cast<int>(mType), mSize, pthread_self(), gGpuThread)
-
-enum class GpuObjectType {
-    Texture = 0,
-    OffscreenBuffer,
-    Layer,
-
-    TypeCount,
-};
-
-class GpuMemoryTracker {
-public:
-    GpuObjectType objectType() { return mType; }
-    int objectSize() { return mSize; }
-
-    static void onGpuContextCreated();
-    static void onGpuContextDestroyed();
-    static void dump();
-    static void dump(std::ostream& stream);
-    static int getInstanceCount(GpuObjectType type);
-    static int getTotalSize(GpuObjectType type);
-    static void onFrameCompleted();
-
-protected:
-    explicit GpuMemoryTracker(GpuObjectType type) : mType(type) {
-        ASSERT_GPU_THREAD();
-        startTrackingObject();
-    }
-
-    ~GpuMemoryTracker() {
-        notifySizeChanged(0);
-        stopTrackingObject();
-    }
-
-    void notifySizeChanged(int newSize);
-
-private:
-    void startTrackingObject();
-    void stopTrackingObject();
-
-    int mSize = 0;
-    GpuObjectType mType;
-};
-
-}  // namespace uirenderer
-}  // namespace android;
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 10e7160..d25fc4b 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -81,7 +81,7 @@
 
 JankTracker::JankTracker(ProfileDataContainer* globalData) {
     mGlobalData = globalData;
-    nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1_s / DeviceInfo::getRefreshRate());
+    nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod();
     nsecs_t sfOffset = DeviceInfo::getCompositorOffset();
     nsecs_t offsetDelta = sfOffset - DeviceInfo::getAppOffset();
     // There are two different offset cases. If the offsetDelta is positive
diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp
index 7921662..a8e36e3 100644
--- a/libs/hwui/ProfileData.cpp
+++ b/libs/hwui/ProfileData.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "ProfileData.h"
+#include "Properties.h"
 
 #include <cinttypes>
 
@@ -102,6 +103,7 @@
         mGPUFrameCounts[i] >>= divider;
         mGPUFrameCounts[i] += other.mGPUFrameCounts[i];
     }
+    mPipelineType = other.mPipelineType;
 }
 
 void ProfileData::dump(int fd) const {
@@ -157,6 +159,7 @@
     mTotalFrameCount = 0;
     mJankFrameCount = 0;
     mStatStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    mPipelineType = Properties::getRenderPipelineType();
 }
 
 void ProfileData::reportFrame(int64_t duration) {
diff --git a/libs/hwui/ProfileData.h b/libs/hwui/ProfileData.h
index ccbffc6..dd3ba66 100644
--- a/libs/hwui/ProfileData.h
+++ b/libs/hwui/ProfileData.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include "Properties.h"
 #include "utils/Macros.h"
 
 #include <utils/Timers.h>
@@ -65,6 +66,7 @@
     uint32_t jankFrameCount() const { return mJankFrameCount; }
     nsecs_t statsStartTime() const { return mStatStartTime; }
     uint32_t jankTypeCount(JankType type) const { return mJankTypeCounts[static_cast<int>(type)]; }
+    RenderPipelineType pipelineType() const { return mPipelineType; }
 
     struct HistogramEntry {
         uint32_t renderTimeMs;
@@ -103,6 +105,9 @@
     uint32_t mTotalFrameCount;
     uint32_t mJankFrameCount;
     nsecs_t mStatStartTime;
+
+    // true if HWUI renders with Vulkan pipeline
+    RenderPipelineType mPipelineType;
 };
 
 // For testing
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 2314072..12681ae 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -746,7 +746,10 @@
     glyphFunc(buffer.glyphs, buffer.pos);
 
     sk_sp<SkTextBlob> textBlob(builder.make());
-    mCanvas->drawTextBlob(textBlob, 0, 0, paintCopy);
+
+    apply_looper(&paintCopy, [&](const SkPaint& p) {
+        mCanvas->drawTextBlob(textBlob, 0, 0, p);
+    });
     drawTextDecorations(x, y, totalAdvance, paintCopy);
 }
 
@@ -783,8 +786,10 @@
         xform[i - start].fTx = pos.x() - tan.y() * y - halfWidth * tan.x();
         xform[i - start].fTy = pos.y() + tan.x() * y - halfWidth * tan.y();
     }
-
-    this->asSkCanvas()->drawTextBlob(builder.make(), 0, 0, paintCopy);
+    auto* finalCanvas = this->asSkCanvas();
+    apply_looper(&paintCopy, [&](const SkPaint& p) {
+        finalCanvas->drawTextBlob(builder.make(), 0, 0, paintCopy);
+    });
 }
 
 // ----------------------------------------------------------------------------
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index f4149b9..84549e8 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -152,7 +152,9 @@
     AHardwareBuffer_describe(hardwareBuffer, &bufferDesc);
     SkImageInfo info = uirenderer::BufferDescriptionToImageInfo(bufferDesc, colorSpace);
 
-    const size_t rowBytes = info.bytesPerPixel() * bufferDesc.stride;
+    // If the stride is 0 we have to use the width as an approximation (eg, compressed buffer)
+    const auto bufferStride = bufferDesc.stride > 0 ? bufferDesc.stride : bufferDesc.width;
+    const size_t rowBytes = info.bytesPerPixel() * bufferStride;
     return sk_sp<Bitmap>(new Bitmap(hardwareBuffer, info, rowBytes, palette));
 }
 
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
new file mode 100644
index 0000000..2c78b02
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ATraceMemoryDump.h"
+
+#include <utils/Trace.h>
+
+#include <cstring>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+// When purgeable is INVALID_TIME it won't be logged at all.
+#define INVALID_TIME -1
+
+/**
+ * Skia invokes the following SkTraceMemoryDump functions:
+ * 1. dumpNumericValue (dumpName, units="bytes", valueName="size")
+ * 2. dumpStringValue (dumpName, valueName="type") [optional -> for example CPU memory does not
+ * invoke dumpStringValue]
+ * 3. dumpNumericValue (dumpName, units="bytes", valueName="purgeable_size") [optional]
+ * 4. setMemoryBacking(dumpName, backingType) [optional -> for example Vulkan GPU resources do not
+ * invoke setMemoryBacking]
+ *
+ * ATraceMemoryDump calculates memory category first by looking at the "type" string passed to
+ * dumpStringValue and then by looking at "backingType" passed to setMemoryBacking.
+ * Only GPU Texture memory is tracked separately and everything else is grouped as one
+ * "GPU Memory" category.
+ */
+static std::unordered_map<const char*, const char*> sResourceMap = {
+        {"malloc", "Graphics CPU Memory"},          // taken from setMemoryBacking(backingType)
+        {"gl_texture", "Graphics Texture Memory"},  // taken from setMemoryBacking(backingType)
+        {"Texture",
+         "Graphics Texture Memory"},  // taken from dumpStringValue(value, valueName="type")
+        // Uncomment categories below to split "GPU Memory" into more brackets for debugging.
+        /*{"vk_buffer", "vk_buffer"},
+        {"gl_renderbuffer", "gl_renderbuffer"},
+        {"gl_buffer", "gl_buffer"},
+        {"RenderTarget", "RenderTarget"},
+        {"Stencil", "Stencil"},
+        {"Path Data", "Path Data"},
+        {"Buffer Object", "Buffer Object"},
+        {"Surface", "Surface"},*/
+};
+
+ATraceMemoryDump::ATraceMemoryDump() {
+    mLastDumpName.reserve(100);
+    mCategory.reserve(100);
+}
+
+void ATraceMemoryDump::dumpNumericValue(const char* dumpName, const char* valueName,
+                                        const char* units, uint64_t value) {
+    if (!strcmp(units, "bytes")) {
+        recordAndResetCountersIfNeeded(dumpName);
+        if (!strcmp(valueName, "size")) {
+            mLastDumpValue = value;
+        } else if (!strcmp(valueName, "purgeable_size")) {
+            mLastPurgeableDumpValue = value;
+        }
+    }
+}
+
+void ATraceMemoryDump::dumpStringValue(const char* dumpName, const char* valueName,
+                                       const char* value) {
+    if (!strcmp(valueName, "type")) {
+        recordAndResetCountersIfNeeded(dumpName);
+        auto categoryIt = sResourceMap.find(value);
+        if (categoryIt != sResourceMap.end()) {
+            mCategory = categoryIt->second;
+        }
+    }
+}
+
+void ATraceMemoryDump::setMemoryBacking(const char* dumpName, const char* backingType,
+                                        const char* backingObjectId) {
+    recordAndResetCountersIfNeeded(dumpName);
+    auto categoryIt = sResourceMap.find(backingType);
+    if (categoryIt != sResourceMap.end()) {
+        mCategory = categoryIt->second;
+    }
+}
+
+/**
+ * startFrame is invoked before dumping anything. It resets counters from the previous frame.
+ * This is important, because if there is no new data for a given category trace would assume
+ * usage has not changed (instead of reporting 0).
+ */
+void ATraceMemoryDump::startFrame() {
+    resetCurrentCounter("");
+    for (auto& it : mCurrentValues) {
+        // Once a category is observed in at least one frame, it is always reported in subsequent
+        // frames (even if it is 0). Not logging a category to ATRACE would mean its value has not
+        // changed since the previous frame, which is not what we want.
+        it.second.time = 0;
+        // If purgeableTime is INVALID_TIME, then logTraces won't log it at all.
+        if (it.second.purgeableTime != INVALID_TIME) {
+            it.second.purgeableTime = 0;
+        }
+    }
+}
+
+/**
+ * logTraces reads from mCurrentValues and logs the counters with ATRACE.
+ */
+void ATraceMemoryDump::logTraces() {
+    // Accumulate data from last dumpName
+    recordAndResetCountersIfNeeded("");
+    for (auto& it : mCurrentValues) {
+        ATRACE_INT64(it.first.c_str(), it.second.time);
+        if (it.second.purgeableTime != INVALID_TIME) {
+            ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableTime);
+        }
+    }
+}
+
+/**
+ * recordAndResetCountersIfNeeded reads memory usage from mLastDumpValue/mLastPurgeableDumpValue and
+ * accumulates in mCurrentValues[category]. It makes provision to create a new category and track
+ * purgeable memory only if there is at least one observation.
+ * recordAndResetCountersIfNeeded won't do anything until all the information for a given dumpName
+ * is received.
+ */
+void ATraceMemoryDump::recordAndResetCountersIfNeeded(const char* dumpName) {
+    if (!mLastDumpName.compare(dumpName)) {
+        // Still waiting for more data for current dumpName.
+        return;
+    }
+
+    // First invocation will have an empty mLastDumpName.
+    if (!mLastDumpName.empty()) {
+        // A new dumpName observed -> store the data already collected.
+        auto memoryCounter = mCurrentValues.find(mCategory);
+        if (memoryCounter != mCurrentValues.end()) {
+            memoryCounter->second.time += mLastDumpValue;
+            if (mLastPurgeableDumpValue != INVALID_TIME) {
+                if (memoryCounter->second.purgeableTime == INVALID_TIME) {
+                    memoryCounter->second.purgeableTime = mLastPurgeableDumpValue;
+                } else {
+                    memoryCounter->second.purgeableTime += mLastPurgeableDumpValue;
+                }
+            }
+        } else {
+            mCurrentValues[mCategory] = {mLastDumpValue, mLastPurgeableDumpValue};
+        }
+    }
+
+    // Reset counters and default category for the newly observed "dumpName".
+    resetCurrentCounter(dumpName);
+}
+
+void ATraceMemoryDump::resetCurrentCounter(const char* dumpName) {
+    mLastDumpValue = 0;
+    mLastPurgeableDumpValue = INVALID_TIME;
+    mLastDumpName = dumpName;
+    // Categories not listed in sResourceMap are reported as "GPU memory"
+    mCategory = "GPU Memory";
+}
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.h b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
new file mode 100644
index 0000000..aa5c401
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <SkString.h>
+#include <SkTraceMemoryDump.h>
+
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class ATraceMemoryDump : public SkTraceMemoryDump {
+public:
+    ATraceMemoryDump();
+    ~ATraceMemoryDump() override {}
+
+    void dumpNumericValue(const char* dumpName, const char* valueName, const char* units,
+                          uint64_t value) override;
+
+    void dumpStringValue(const char* dumpName, const char* valueName, const char* value) override;
+
+    LevelOfDetail getRequestedDetails() const override {
+        return SkTraceMemoryDump::kLight_LevelOfDetail;
+    }
+
+    bool shouldDumpWrappedObjects() const override { return false; }
+
+    void setMemoryBacking(const char* dumpName, const char* backingType,
+                          const char* backingObjectId) override;
+
+    void setDiscardableMemoryBacking(const char*, const SkDiscardableMemory&) override {}
+
+    void startFrame();
+
+    void logTraces();
+
+private:
+    std::string mLastDumpName;
+
+    uint64_t mLastDumpValue;
+
+    uint64_t mLastPurgeableDumpValue;
+
+    std::string mCategory;
+
+    struct TraceValue {
+        uint64_t time;
+        uint64_t purgeableTime;
+    };
+
+    // keys are define in sResourceMap
+    std::unordered_map<std::string, TraceValue> mCurrentValues;
+
+    void recordAndResetCountersIfNeeded(const char* dumpName);
+
+    void resetCurrentCounter(const char* dumpName);
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 11dc013..35a885f 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -457,7 +457,10 @@
                                    const Rect& contentDrawBounds, SkCanvas* canvas,
                                    const SkMatrix& preTransform) {
     SkAutoCanvasRestore saver(canvas, true);
-    canvas->androidFramework_setDeviceClipRestriction(preTransform.mapRect(clip).roundOut());
+    auto clipRestriction = preTransform.mapRect(clip).roundOut();
+    canvas->androidFramework_setDeviceClipRestriction(clipRestriction);
+    canvas->drawAnnotation(SkRect::Make(clipRestriction), "AndroidDeviceClipRestriction",
+        nullptr);
     canvas->concat(preTransform);
 
     // STOPSHIP: Revert, temporary workaround to clear always F16 frame buffer for b/74976293
diff --git a/libs/hwui/protos/graphicsstats.proto b/libs/hwui/protos/graphicsstats.proto
index 0cd5c62..dd5676c 100644
--- a/libs/hwui/protos/graphicsstats.proto
+++ b/libs/hwui/protos/graphicsstats.proto
@@ -29,6 +29,11 @@
 }
 
 message GraphicsStatsProto {
+    enum PipelineType {
+        GL = 0;
+        VULKAN = 1;
+    }
+
     // The package name of the app
     optional string package_name = 1;
 
@@ -49,6 +54,9 @@
 
     // The gpu frame time histogram for the package
     repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7;
+
+    // HWUI renders pipeline type: GL or Vulkan
+    optional PipelineType pipeline = 8;
 }
 
 message GraphicsStatsJankSummaryProto {
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index fad9440..7e8c96d 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -16,7 +16,6 @@
 #include "renderstate/RenderState.h"
 
 #include "renderthread/RenderThread.h"
-#include "GpuMemoryTracker.h"
 
 namespace android {
 namespace uirenderer {
@@ -25,15 +24,10 @@
     mThreadId = pthread_self();
 }
 
-void RenderState::onContextCreated() {
-    GpuMemoryTracker::onGpuContextCreated();
-}
-
 void RenderState::onContextDestroyed() {
     for(auto callback : mContextCallbacks) {
         callback->onContextDestroyed();
     }
-    GpuMemoryTracker::onGpuContextDestroyed();
 }
 
 void RenderState::postDecStrong(VirtualLightRefBase* object) {
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index ff5d02f..e08d32a 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -62,7 +62,6 @@
     ~RenderState() {}
 
     // Context notifications are only to be triggered by renderthread::RenderThread
-    void onContextCreated();
     void onContextDestroyed();
 
     std::set<IGpuContextCallback*> mContextCallbacks;
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index eaed46c..d177855 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -20,10 +20,12 @@
 #include "Layer.h"
 #include "Properties.h"
 #include "RenderThread.h"
+#include "pipeline/skia/ATraceMemoryDump.h"
 #include "pipeline/skia/ShaderCache.h"
 #include "pipeline/skia/SkiaMemoryTracer.h"
 #include "renderstate/RenderState.h"
 #include "thread/CommonPool.h"
+#include <utils/Trace.h>
 
 #include <GrContextOptions.h>
 #include <SkExecutor.h>
@@ -184,6 +186,18 @@
     gpuTracer.logTotals(log);
 }
 
+void CacheManager::onFrameCompleted() {
+    if (ATRACE_ENABLED()) {
+        static skiapipeline::ATraceMemoryDump tracer;
+        tracer.startFrame();
+        SkGraphics::DumpMemoryStatistics(&tracer);
+        if (mGrContext) {
+            mGrContext->dumpMemoryStatistics(&tracer);
+        }
+        tracer.logTraces();
+    }
+}
+
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index 968251e..b009cc4 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -50,6 +50,7 @@
 
     size_t getCacheSize() const { return mMaxResourceBytes; }
     size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; }
+    void onFrameCompleted();
 
 private:
     friend class RenderThread;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 8490221..5993e17 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -16,7 +16,6 @@
 
 #include "CanvasContext.h"
 
-#include <GpuMemoryTracker.h>
 #include <apex/window.h>
 #include <fcntl.h>
 #include <strings.h>
@@ -558,7 +557,7 @@
         mJankTracker.finishGpuDraw(*forthBehind);
     }
 
-    GpuMemoryTracker::onFrameCompleted();
+    mRenderThread.cacheManager().onFrameCompleted();
 }
 
 // Called by choreographer to do an RT-driven animation
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index a446858..cae3e3b 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -34,7 +34,6 @@
 #include <GrContextOptions.h>
 #include <gl/GrGLInterface.h>
 
-#include <gui/DisplayEventReceiver.h>
 #include <sys/resource.h>
 #include <utils/Condition.h>
 #include <utils/Log.h>
@@ -45,53 +44,43 @@
 namespace uirenderer {
 namespace renderthread {
 
-// Number of events to read at a time from the DisplayEventReceiver pipe.
-// The value should be large enough that we can quickly drain the pipe
-// using just a few large reads.
-static const size_t EVENT_BUFFER_SIZE = 100;
-
 static bool gHasRenderThreadInstance = false;
 
 static JVMAttachHook gOnStartHook = nullptr;
 
-class DisplayEventReceiverWrapper : public VsyncSource {
+void RenderThread::frameCallback(int64_t frameTimeNanos, void* data) {
+    RenderThread* rt = reinterpret_cast<RenderThread*>(data);
+    rt->mVsyncRequested = false;
+    if (rt->timeLord().vsyncReceived(frameTimeNanos) && !rt->mFrameCallbackTaskPending) {
+        ATRACE_NAME("queue mFrameCallbackTask");
+        rt->mFrameCallbackTaskPending = true;
+        nsecs_t runAt = (frameTimeNanos + rt->mDispatchFrameDelay);
+        rt->queue().postAt(runAt, [=]() { rt->dispatchFrameCallbacks(); });
+    }
+}
+
+void RenderThread::refreshRateCallback(int64_t vsyncPeriod, void* data) {
+    ATRACE_NAME("refreshRateCallback");
+    RenderThread* rt = reinterpret_cast<RenderThread*>(data);
+    DeviceInfo::get()->onRefreshRateChanged(vsyncPeriod);
+    rt->setupFrameInterval();
+}
+
+class ChoreographerSource : public VsyncSource {
 public:
-    DisplayEventReceiverWrapper(std::unique_ptr<DisplayEventReceiver>&& receiver,
-            const std::function<void()>& onDisplayConfigChanged)
-            : mDisplayEventReceiver(std::move(receiver))
-            , mOnDisplayConfigChanged(onDisplayConfigChanged) {}
+    ChoreographerSource(RenderThread* renderThread) : mRenderThread(renderThread) {}
 
     virtual void requestNextVsync() override {
-        status_t status = mDisplayEventReceiver->requestNextVsync();
-        LOG_ALWAYS_FATAL_IF(status != NO_ERROR, "requestNextVsync failed with status: %d", status);
+        AChoreographer_postFrameCallback64(mRenderThread->mChoreographer,
+                                           RenderThread::frameCallback, mRenderThread);
     }
 
-    virtual nsecs_t latestVsyncEvent() override {
-        DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
-        nsecs_t latest = 0;
-        ssize_t n;
-        while ((n = mDisplayEventReceiver->getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
-            for (ssize_t i = 0; i < n; i++) {
-                const DisplayEventReceiver::Event& ev = buf[i];
-                switch (ev.header.type) {
-                    case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
-                        latest = ev.header.timestamp;
-                        break;
-                    case DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED:
-                        mOnDisplayConfigChanged();
-                        break;
-                }
-            }
-        }
-        if (n < 0) {
-            ALOGW("Failed to get events from display event receiver, status=%d", status_t(n));
-        }
-        return latest;
+    virtual void drainPendingEvents() override {
+        AChoreographer_handlePendingEvents(mRenderThread->mChoreographer, mRenderThread);
     }
 
 private:
-    std::unique_ptr<DisplayEventReceiver> mDisplayEventReceiver;
-    std::function<void()> mOnDisplayConfigChanged;
+    RenderThread* mRenderThread;
 };
 
 class DummyVsyncSource : public VsyncSource {
@@ -99,11 +88,14 @@
     DummyVsyncSource(RenderThread* renderThread) : mRenderThread(renderThread) {}
 
     virtual void requestNextVsync() override {
-        mRenderThread->queue().postDelayed(16_ms,
-                                           [this]() { mRenderThread->drainDisplayEventQueue(); });
+        mRenderThread->queue().postDelayed(16_ms, [this]() {
+            RenderThread::frameCallback(systemTime(SYSTEM_TIME_MONOTONIC), mRenderThread);
+        });
     }
 
-    virtual nsecs_t latestVsyncEvent() override { return systemTime(SYSTEM_TIME_MONOTONIC); }
+    virtual void drainPendingEvents() override {
+        RenderThread::frameCallback(systemTime(SYSTEM_TIME_MONOTONIC), mRenderThread);
+    }
 
 private:
     RenderThread* mRenderThread;
@@ -145,29 +137,24 @@
 }
 
 RenderThread::~RenderThread() {
+    // Note that if this fatal assertion is removed then member variables must
+    // be properly destroyed.
     LOG_ALWAYS_FATAL("Can't destroy the render thread");
 }
 
-void RenderThread::initializeDisplayEventReceiver() {
-    LOG_ALWAYS_FATAL_IF(mVsyncSource, "Initializing a second DisplayEventReceiver?");
+void RenderThread::initializeChoreographer() {
+    LOG_ALWAYS_FATAL_IF(mVsyncSource, "Initializing a second Choreographer?");
 
     if (!Properties::isolatedProcess) {
-        auto receiver = std::make_unique<DisplayEventReceiver>(
-            ISurfaceComposer::eVsyncSourceApp,
-            ISurfaceComposer::eConfigChangedDispatch);
-        status_t status = receiver->initCheck();
-        LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
-                            "Initialization of DisplayEventReceiver "
-                            "failed with status: %d",
-                            status);
+        mChoreographer = AChoreographer_create();
+        LOG_ALWAYS_FATAL_IF(mChoreographer == nullptr, "Initialization of Choreographer failed");
+        AChoreographer_registerRefreshRateCallback(mChoreographer,
+                                                   RenderThread::refreshRateCallback, this);
 
         // Register the FD
-        mLooper->addFd(receiver->getFd(), 0, Looper::EVENT_INPUT,
-                       RenderThread::displayEventReceiverCallback, this);
-        mVsyncSource = new DisplayEventReceiverWrapper(std::move(receiver), [this] {
-            DeviceInfo::get()->onDisplayConfigChanged();
-            setupFrameInterval();
-        });
+        mLooper->addFd(AChoreographer_getFd(mChoreographer), 0, Looper::EVENT_INPUT,
+                       RenderThread::choreographerCallback, this);
+        mVsyncSource = new ChoreographerSource(this);
     } else {
         mVsyncSource = new DummyVsyncSource(this);
     }
@@ -175,7 +162,7 @@
 
 void RenderThread::initThreadLocals() {
     setupFrameInterval();
-    initializeDisplayEventReceiver();
+    initializeChoreographer();
     mEglManager = new EglManager();
     mRenderState = new RenderState(*this);
     mVkManager = new VulkanManager();
@@ -183,7 +170,7 @@
 }
 
 void RenderThread::setupFrameInterval() {
-    nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1000000000 / DeviceInfo::getRefreshRate());
+    nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod();
     mTimeLord.setFrameInterval(frameIntervalNanos);
     mDispatchFrameDelay = static_cast<nsecs_t>(frameIntervalNanos * .25f);
 }
@@ -283,12 +270,11 @@
     }
     mGrContext = std::move(context);
     if (mGrContext) {
-        mRenderState->onContextCreated();
         DeviceInfo::setMaxTextureSize(mGrContext->maxRenderTargetSize());
     }
 }
 
-int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) {
+int RenderThread::choreographerCallback(int fd, int events, void* data) {
     if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
         ALOGE("Display event receiver pipe was closed or an error occurred.  "
               "events=0x%x",
@@ -302,24 +288,10 @@
               events);
         return 1;  // keep the callback
     }
+    RenderThread* rt = reinterpret_cast<RenderThread*>(data);
+    AChoreographer_handlePendingEvents(rt->mChoreographer, data);
 
-    reinterpret_cast<RenderThread*>(data)->drainDisplayEventQueue();
-
-    return 1;  // keep the callback
-}
-
-void RenderThread::drainDisplayEventQueue() {
-    ATRACE_CALL();
-    nsecs_t vsyncEvent = mVsyncSource->latestVsyncEvent();
-    if (vsyncEvent > 0) {
-        mVsyncRequested = false;
-        if (mTimeLord.vsyncReceived(vsyncEvent) && !mFrameCallbackTaskPending) {
-            ATRACE_NAME("queue mFrameCallbackTask");
-            mFrameCallbackTaskPending = true;
-            nsecs_t runAt = (vsyncEvent + mDispatchFrameDelay);
-            queue().postAt(runAt, [this]() { dispatchFrameCallbacks(); });
-        }
-    }
+    return 1;
 }
 
 void RenderThread::dispatchFrameCallbacks() {
@@ -360,7 +332,7 @@
         processQueue();
 
         if (mPendingRegistrationFrameCallbacks.size() && !mFrameCallbackTaskPending) {
-            drainDisplayEventQueue();
+            mVsyncSource->drainPendingEvents();
             mFrameCallbacks.insert(mPendingRegistrationFrameCallbacks.begin(),
                                    mPendingRegistrationFrameCallbacks.end());
             mPendingRegistrationFrameCallbacks.clear();
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index da79e97..8be46a6 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -19,6 +19,7 @@
 
 #include <GrContext.h>
 #include <SkBitmap.h>
+#include <apex/choreographer.h>
 #include <cutils/compiler.h>
 #include <thread/ThreadBase.h>
 #include <utils/Looper.h>
@@ -73,10 +74,11 @@
 
 struct VsyncSource {
     virtual void requestNextVsync() = 0;
-    virtual nsecs_t latestVsyncEvent() = 0;
+    virtual void drainPendingEvents() = 0;
     virtual ~VsyncSource() {}
 };
 
+class ChoreographerSource;
 class DummyVsyncSource;
 
 typedef void (*JVMAttachHook)(const char* name);
@@ -136,6 +138,7 @@
     friend class DispatchFrameCallbacks;
     friend class RenderProxy;
     friend class DummyVsyncSource;
+    friend class ChoreographerSource;
     friend class android::uirenderer::AutoBackendTextureRelease;
     friend class android::uirenderer::TestUtils;
     friend class android::uirenderer::WebViewFunctor;
@@ -149,13 +152,21 @@
     static RenderThread& getInstance();
 
     void initThreadLocals();
-    void initializeDisplayEventReceiver();
+    void initializeChoreographer();
     void setupFrameInterval();
-    static int displayEventReceiverCallback(int fd, int events, void* data);
+    // Callbacks for choreographer events:
+    // choreographerCallback will call AChoreograper_handleEvent to call the
+    // corresponding callbacks for each display event type
+    static int choreographerCallback(int fd, int events, void* data);
+    // Callback that will be run on vsync ticks.
+    static void frameCallback(int64_t frameTimeNanos, void* data);
+    // Callback that will be run whenver there is a refresh rate change.
+    static void refreshRateCallback(int64_t vsyncPeriod, void* data);
     void drainDisplayEventQueue();
     void dispatchFrameCallbacks();
     void requestVsync();
 
+    AChoreographer* mChoreographer;
     VsyncSource* mVsyncSource;
     bool mVsyncRequested;
     std::set<IFrameCallback*> mFrameCallbacks;
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index 12c5b83..c418617 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -16,24 +16,28 @@
 
 #include "GraphicsStatsService.h"
 
-#include "JankTracker.h"
-#include "protos/graphicsstats.pb.h"
-
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
-#include <log/log.h>
-
 #include <errno.h>
 #include <fcntl.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
 #include <inttypes.h>
+#include <log/log.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <algorithm>
+#include <map>
+#include <vector>
+
+#include "JankTracker.h"
+#include "protos/graphicsstats.pb.h"
+
 namespace android {
 namespace uirenderer {
 
 using namespace google::protobuf;
+using namespace uirenderer::protos;
 
 constexpr int32_t sCurrentFileVersion = 1;
 constexpr int32_t sHeaderSize = 4;
@@ -42,9 +46,9 @@
 constexpr int sHistogramSize = ProfileData::HistogramSize();
 constexpr int sGPUHistogramSize = ProfileData::GPUHistogramSize();
 
-static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto,
-                                      const std::string& package, int64_t versionCode,
-                                      int64_t startTime, int64_t endTime, const ProfileData* data);
+static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package,
+                                      int64_t versionCode, int64_t startTime, int64_t endTime,
+                                      const ProfileData* data);
 static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd);
 
 class FileDescriptor {
@@ -57,7 +61,7 @@
         }
     }
     bool valid() { return mFd != -1; }
-    operator int() { return mFd; } // NOLINT(google-explicit-constructor)
+    operator int() { return mFd; }  // NOLINT(google-explicit-constructor)
 
 private:
     int mFd;
@@ -167,6 +171,8 @@
     }
     proto->set_package_name(package);
     proto->set_version_code(versionCode);
+    proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ?
+            GraphicsStatsProto_PipelineType_GL : GraphicsStatsProto_PipelineType_VULKAN);
     auto summary = proto->mutable_summary();
     summary->set_total_frames(summary->total_frames() + data->totalFrameCount());
     summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount());
@@ -179,8 +185,8 @@
     summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
                                           data->jankTypeCount(kSlowSync));
     summary->set_slow_draw_count(summary->slow_draw_count() + data->jankTypeCount(kSlowRT));
-    summary->set_missed_deadline_count(summary->missed_deadline_count()
-            + data->jankTypeCount(kMissedDeadline));
+    summary->set_missed_deadline_count(summary->missed_deadline_count() +
+                                       data->jankTypeCount(kMissedDeadline));
 
     bool creatingHistogram = false;
     if (proto->histogram_size() == 0) {
@@ -365,17 +371,69 @@
 
 class GraphicsStatsService::Dump {
 public:
-    Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
+    Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {
+        if (mFd == -1 && mType == DumpType::Protobuf) {
+            mType = DumpType::ProtobufStatsd;
+        }
+    }
     int fd() { return mFd; }
     DumpType type() { return mType; }
     protos::GraphicsStatsServiceDumpProto& proto() { return mProto; }
+    void mergeStat(const protos::GraphicsStatsProto& stat);
+    void updateProto();
 
 private:
+    // use package name and app version for a key
+    typedef std::pair<std::string, int64_t> DumpKey;
+
+    std::map<DumpKey, protos::GraphicsStatsProto> mStats;
     int mFd;
     DumpType mType;
     protos::GraphicsStatsServiceDumpProto mProto;
 };
 
+void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) {
+    auto dumpKey = std::make_pair(stat.package_name(), stat.version_code());
+    auto findIt = mStats.find(dumpKey);
+    if (findIt == mStats.end()) {
+        mStats[dumpKey] = stat;
+    } else {
+        auto summary = findIt->second.mutable_summary();
+        summary->set_total_frames(summary->total_frames() + stat.summary().total_frames());
+        summary->set_janky_frames(summary->janky_frames() + stat.summary().janky_frames());
+        summary->set_missed_vsync_count(summary->missed_vsync_count() +
+                                        stat.summary().missed_vsync_count());
+        summary->set_high_input_latency_count(summary->high_input_latency_count() +
+                                              stat.summary().high_input_latency_count());
+        summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() +
+                                          stat.summary().slow_ui_thread_count());
+        summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
+                                              stat.summary().slow_bitmap_upload_count());
+        summary->set_slow_draw_count(summary->slow_draw_count() + stat.summary().slow_draw_count());
+        summary->set_missed_deadline_count(summary->missed_deadline_count() +
+                                           stat.summary().missed_deadline_count());
+        for (int bucketIndex = 0; bucketIndex < findIt->second.histogram_size(); bucketIndex++) {
+            auto bucket = findIt->second.mutable_histogram(bucketIndex);
+            bucket->set_frame_count(bucket->frame_count() +
+                                    stat.histogram(bucketIndex).frame_count());
+        }
+        for (int bucketIndex = 0; bucketIndex < findIt->second.gpu_histogram_size();
+             bucketIndex++) {
+            auto bucket = findIt->second.mutable_gpu_histogram(bucketIndex);
+            bucket->set_frame_count(bucket->frame_count() +
+                                    stat.gpu_histogram(bucketIndex).frame_count());
+        }
+        findIt->second.set_stats_start(std::min(findIt->second.stats_start(), stat.stats_start()));
+        findIt->second.set_stats_end(std::max(findIt->second.stats_end(), stat.stats_end()));
+    }
+}
+
+void GraphicsStatsService::Dump::updateProto() {
+    for (auto& stat : mStats) {
+        mProto.add_stats()->CopyFrom(stat.second);
+    }
+}
+
 GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
     return new Dump(outFd, type);
 }
@@ -396,8 +454,9 @@
               path.empty() ? "<empty>" : path.c_str(), data);
         return;
     }
-
-    if (dump->type() == DumpType::Protobuf) {
+    if (dump->type() == DumpType::ProtobufStatsd) {
+        dump->mergeStat(statsProto);
+    } else if (dump->type() == DumpType::Protobuf) {
         dump->proto().add_stats()->CopyFrom(statsProto);
     } else {
         dumpAsTextToFd(&statsProto, dump->fd());
@@ -409,7 +468,9 @@
     if (!parseFromFile(path, &statsProto)) {
         return;
     }
-    if (dump->type() == DumpType::Protobuf) {
+    if (dump->type() == DumpType::ProtobufStatsd) {
+        dump->mergeStat(statsProto);
+    } else if (dump->type() == DumpType::Protobuf) {
         dump->proto().add_stats()->CopyFrom(statsProto);
     } else {
         dumpAsTextToFd(&statsProto, dump->fd());
@@ -424,5 +485,79 @@
     delete dump;
 }
 
+class MemOutputStreamLite : public io::ZeroCopyOutputStream {
+public:
+    explicit MemOutputStreamLite() : mCopyAdapter(), mImpl(&mCopyAdapter) {}
+    virtual ~MemOutputStreamLite() {}
+
+    virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); }
+
+    virtual void BackUp(int count) override { mImpl.BackUp(count); }
+
+    virtual int64 ByteCount() const override { return mImpl.ByteCount(); }
+
+    bool Flush() { return mImpl.Flush(); }
+
+    void copyData(const DumpMemoryFn& reader, void* param1, void* param2) {
+        int bufferOffset = 0;
+        int totalSize = mCopyAdapter.mBuffersSize - mCopyAdapter.mCurrentBufferUnusedSize;
+        int totalDataLeft = totalSize;
+        for (auto& it : mCopyAdapter.mBuffers) {
+            int bufferSize = std::min(totalDataLeft, (int)it.size());  // last buffer is not full
+            reader(it.data(), bufferOffset, bufferSize, totalSize, param1, param2);
+            bufferOffset += bufferSize;
+            totalDataLeft -= bufferSize;
+        }
+    }
+
+private:
+    struct MemAdapter : public io::CopyingOutputStream {
+        // Data is stored in an array of buffers.
+        // JNI SetByteArrayRegion assembles data in one continuous Java byte[] buffer.
+        std::vector<std::vector<unsigned char>> mBuffers;
+        int mBuffersSize = 0;                     // total bytes allocated in mBuffers
+        int mCurrentBufferUnusedSize = 0;         // unused bytes in the last buffer mBuffers.back()
+        unsigned char* mCurrentBuffer = nullptr;  // pointer to next free byte in mBuffers.back()
+
+        explicit MemAdapter() {}
+        virtual ~MemAdapter() {}
+
+        virtual bool Write(const void* buffer, int size) override {
+            while (size > 0) {
+                if (0 == mCurrentBufferUnusedSize) {
+                    mCurrentBufferUnusedSize =
+                            std::max(size, mBuffersSize ? 2 * mBuffersSize : 10000);
+                    mBuffers.emplace_back();
+                    mBuffers.back().resize(mCurrentBufferUnusedSize);
+                    mCurrentBuffer = mBuffers.back().data();
+                    mBuffersSize += mCurrentBufferUnusedSize;
+                }
+                int dataMoved = std::min(mCurrentBufferUnusedSize, size);
+                memcpy(mCurrentBuffer, buffer, dataMoved);
+                mCurrentBufferUnusedSize -= dataMoved;
+                mCurrentBuffer += dataMoved;
+                buffer = reinterpret_cast<const unsigned char*>(buffer) + dataMoved;
+                size -= dataMoved;
+            }
+            return true;
+        }
+    };
+
+    MemOutputStreamLite::MemAdapter mCopyAdapter;
+    io::CopyingOutputStreamAdaptor mImpl;
+};
+
+void GraphicsStatsService::finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1,
+                                              void* param2) {
+    MemOutputStreamLite stream;
+    dump->updateProto();
+    bool success = dump->proto().SerializeToZeroCopyStream(&stream) && stream.Flush();
+    delete dump;
+    if (!success) {
+        return;
+    }
+    stream.copyData(reader, param1, param2);
+}
+
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h
index 389f599..4bed9633 100644
--- a/libs/hwui/service/GraphicsStatsService.h
+++ b/libs/hwui/service/GraphicsStatsService.h
@@ -27,6 +27,9 @@
 class GraphicsStatsProto;
 }
 
+typedef void (*DumpMemoryFn)(void* buffer, int bufferOffset, int bufferSize, int totalSize,
+                             void* param1, void* param2);
+
 /*
  * The exported entry points used by GraphicsStatsService.java in f/b/services/core
  *
@@ -40,6 +43,7 @@
     enum class DumpType {
         Text,
         Protobuf,
+        ProtobufStatsd,
     };
 
     ANDROID_API static void saveBuffer(const std::string& path, const std::string& package,
@@ -52,6 +56,8 @@
                                       int64_t startTime, int64_t endTime, const ProfileData* data);
     ANDROID_API static void addToDump(Dump* dump, const std::string& path);
     ANDROID_API static void finishDump(Dump* dump);
+    ANDROID_API static void finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1,
+                                               void* param2);
 
     // Visible for testing
     static bool parseFromFile(const std::string& path, protos::GraphicsStatsProto* output);
diff --git a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
deleted file mode 100644
index dac888c..0000000
--- a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <GpuMemoryTracker.h>
-#include <gtest/gtest.h>
-
-#include "renderthread/EglManager.h"
-#include "renderthread/RenderThread.h"
-#include "tests/common/TestUtils.h"
-
-#include <utils/StrongPointer.h>
-
-using namespace android;
-using namespace android::uirenderer;
-using namespace android::uirenderer::renderthread;
-
-class TestGPUObject : public GpuMemoryTracker {
-public:
-    TestGPUObject() : GpuMemoryTracker(GpuObjectType::Texture) {}
-
-    void changeSize(int newSize) { notifySizeChanged(newSize); }
-};
-
-// Other tests may have created a renderthread and EGL context.
-// This will destroy the EGLContext on RenderThread if it exists so that the
-// current thread can spoof being a GPU thread
-static void destroyEglContext() {
-    if (TestUtils::isRenderThreadRunning()) {
-        TestUtils::runOnRenderThread([](RenderThread& thread) { thread.destroyRenderingContext(); });
-    }
-}
-
-TEST(GpuMemoryTracker, sizeCheck) {
-    destroyEglContext();
-
-    GpuMemoryTracker::onGpuContextCreated();
-    ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-    ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
-    {
-        TestGPUObject myObj;
-        ASSERT_EQ(1, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
-        myObj.changeSize(500);
-        ASSERT_EQ(500, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-        myObj.changeSize(1000);
-        ASSERT_EQ(1000, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-        myObj.changeSize(300);
-        ASSERT_EQ(300, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-    }
-    ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-    ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
-    GpuMemoryTracker::onGpuContextDestroyed();
-}
diff --git a/location/java/android/location/Criteria.java b/location/java/android/location/Criteria.java
index 1370b10..26f73f7 100644
--- a/location/java/android/location/Criteria.java
+++ b/location/java/android/location/Criteria.java
@@ -16,9 +16,16 @@
 
 package android.location;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * A class indicating the application criteria for selecting a
  * location provider. Providers may be ordered according to accuracy,
@@ -26,6 +33,25 @@
  * cost.
  */
 public class Criteria implements Parcelable {
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({NO_REQUIREMENT, POWER_LOW, POWER_MEDIUM, POWER_HIGH})
+    public @interface PowerRequirement {
+    }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({NO_REQUIREMENT, ACCURACY_LOW, ACCURACY_MEDIUM, ACCURACY_HIGH})
+    public @interface AccuracyRequirement {
+    }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({NO_REQUIREMENT, ACCURACY_FINE, ACCURACY_COARSE})
+    public @interface LocationAccuracyRequirement {
+    }
+
     /**
      * A constant indicating that the application does not choose to
      * place requirement on a particular feature.
@@ -81,15 +107,15 @@
      */
     public static final int ACCURACY_HIGH = 3;
 
-    private int mHorizontalAccuracy    = NO_REQUIREMENT;
-    private int mVerticalAccuracy      = NO_REQUIREMENT;
-    private int mSpeedAccuracy         = NO_REQUIREMENT;
-    private int mBearingAccuracy       = NO_REQUIREMENT;
-    private int mPowerRequirement      = NO_REQUIREMENT;
-    private boolean mAltitudeRequired  = false;
-    private boolean mBearingRequired   = false;
-    private boolean mSpeedRequired     = false;
-    private boolean mCostAllowed       = false;
+    private int mHorizontalAccuracy = NO_REQUIREMENT;
+    private int mVerticalAccuracy = NO_REQUIREMENT;
+    private int mSpeedAccuracy = NO_REQUIREMENT;
+    private int mBearingAccuracy = NO_REQUIREMENT;
+    private int mPowerRequirement = NO_REQUIREMENT;
+    private boolean mAltitudeRequired = false;
+    private boolean mBearingRequired = false;
+    private boolean mSpeedRequired = false;
+    private boolean mCostAllowed = false;
 
     /**
      * Constructs a new Criteria object.  The new object will have no
@@ -97,7 +123,8 @@
      * require altitude, speed, or bearing; and will not allow monetary
      * cost.
      */
-    public Criteria() {}
+    public Criteria() {
+    }
 
     /**
      * Constructs a new Criteria object that is a copy of the given criteria.
@@ -115,125 +142,121 @@
     }
 
     /**
-     * Indicates the desired horizontal accuracy (latitude and longitude).
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM},
-     * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}.
-     * More accurate location may consume more power and may take longer.
+     * Indicates the desired horizontal accuracy (latitude and longitude). Accuracy may be
+     * {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH} or
+     * {@link #NO_REQUIREMENT}. More accurate location may consume more power and may take longer.
      *
      * @throws IllegalArgumentException if accuracy is not one of the supported constants
      */
-    public void setHorizontalAccuracy(int accuracy) {
-        if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) {
-            throw new IllegalArgumentException("accuracy=" + accuracy);
-        }
-        mHorizontalAccuracy = accuracy;
+    public void setHorizontalAccuracy(@AccuracyRequirement int accuracy) {
+        mHorizontalAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT,
+                ACCURACY_HIGH, "accuracy");
     }
 
     /**
      * Returns a constant indicating the desired horizontal accuracy (latitude and longitude).
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM},
-     * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}.
+     *
+     * @see #setHorizontalAccuracy(int)
      */
+    @AccuracyRequirement
     public int getHorizontalAccuracy() {
         return mHorizontalAccuracy;
     }
 
     /**
-     * Indicates the desired vertical accuracy (altitude).
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM},
-     * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}.
-     * More accurate location may consume more power and may take longer.
+     * Indicates the desired vertical accuracy (altitude). Accuracy may be {@link #ACCURACY_LOW},
+     * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. More accurate
+     * location may consume more power and may take longer.
      *
      * @throws IllegalArgumentException if accuracy is not one of the supported constants
      */
-    public void setVerticalAccuracy(int accuracy) {
-        if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) {
-            throw new IllegalArgumentException("accuracy=" + accuracy);
-        }
-        mVerticalAccuracy = accuracy;
+    public void setVerticalAccuracy(@AccuracyRequirement int accuracy) {
+        mVerticalAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT,
+                ACCURACY_HIGH, "accuracy");
     }
 
     /**
      * Returns a constant indicating the desired vertical accuracy (altitude).
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
-     * or {@link #NO_REQUIREMENT}.
+     *
+     * @see #setVerticalAccuracy(int)
      */
+    @AccuracyRequirement
     public int getVerticalAccuracy() {
         return mVerticalAccuracy;
     }
 
     /**
-     * Indicates the desired speed accuracy.
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
-     * or {@link #NO_REQUIREMENT}.
-     * More accurate location may consume more power and may take longer.
+     * Indicates the desired speed accuracy. Accuracy may be {@link #ACCURACY_LOW},
+     * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH}, or {@link #NO_REQUIREMENT}. More accurate
+     * location may consume more power and may take longer.
      *
      * @throws IllegalArgumentException if accuracy is not one of the supported constants
      */
-    public void setSpeedAccuracy(int accuracy) {
-        if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) {
-            throw new IllegalArgumentException("accuracy=" + accuracy);
-        }
-        mSpeedAccuracy = accuracy;
+    public void setSpeedAccuracy(@AccuracyRequirement int accuracy) {
+        mSpeedAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, ACCURACY_HIGH,
+                "accuracy");
     }
 
     /**
-     * Returns a constant indicating the desired speed accuracy
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
-     * or {@link #NO_REQUIREMENT}.
+     * Returns a constant indicating the desired speed accuracy.
+     *
+     * @see #setSpeedAccuracy(int)
      */
+    @AccuracyRequirement
     public int getSpeedAccuracy() {
         return mSpeedAccuracy;
     }
 
     /**
-     * Indicates the desired bearing accuracy.
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
-     * or {@link #NO_REQUIREMENT}.
-     * More accurate location may consume more power and may take longer.
+     * Indicates the desired bearing accuracy. Accuracy may be {@link #ACCURACY_LOW},
+     * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH}, or {@link #NO_REQUIREMENT}. More accurate
+     * location may consume more power and may take longer.
      *
      * @throws IllegalArgumentException if accuracy is not one of the supported constants
      */
-    public void setBearingAccuracy(int accuracy) {
-        if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) {
-            throw new IllegalArgumentException("accuracy=" + accuracy);
-        }
-        mBearingAccuracy = accuracy;
+    public void setBearingAccuracy(@AccuracyRequirement int accuracy) {
+        mBearingAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT,
+                ACCURACY_HIGH, "accuracy");
     }
 
     /**
      * Returns a constant indicating the desired bearing accuracy.
-     * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
-     * or {@link #NO_REQUIREMENT}.
+     *
+     * @see #setBearingAccuracy(int)
      */
+    @AccuracyRequirement
     public int getBearingAccuracy() {
         return mBearingAccuracy;
     }
 
     /**
-     * Indicates the desired accuracy for latitude and longitude. Accuracy
-     * may be {@link #ACCURACY_FINE} if desired location
-     * is fine, else it can be {@link #ACCURACY_COARSE}.
-     * More accurate location may consume more power and may take longer.
+     * Indicates the desired accuracy for latitude and longitude. Accuracy may be
+     * {@link #ACCURACY_FINE} or {@link #ACCURACY_COARSE}. More accurate location may consume more
+     * power and may take longer.
      *
      * @throws IllegalArgumentException if accuracy is not one of the supported constants
      */
-    public void setAccuracy(int accuracy) {
-        if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_COARSE) {
-            throw new IllegalArgumentException("accuracy=" + accuracy);
-        }
-        if (accuracy == ACCURACY_FINE) {
-            mHorizontalAccuracy = ACCURACY_HIGH;
-        } else {
-            mHorizontalAccuracy = ACCURACY_LOW;
+    public void setAccuracy(@LocationAccuracyRequirement int accuracy) {
+        Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, ACCURACY_COARSE, "accuracy");
+        switch (accuracy) {
+            case NO_REQUIREMENT:
+                setHorizontalAccuracy(NO_REQUIREMENT);
+                break;
+            case ACCURACY_FINE:
+                setHorizontalAccuracy(ACCURACY_HIGH);
+                break;
+            case ACCURACY_COARSE:
+                setHorizontalAccuracy(ACCURACY_LOW);
+                break;
         }
     }
 
     /**
-     * Returns a constant indicating desired accuracy of location
-     * Accuracy may be {@link #ACCURACY_FINE} if desired location
-     * is fine, else it can be {@link #ACCURACY_COARSE}.
+     * Returns a constant indicating desired accuracy of location.
+     *
+     * @see #setAccuracy(int)
      */
+    @LocationAccuracyRequirement
     public int getAccuracy() {
         if (mHorizontalAccuracy >= ACCURACY_HIGH) {
             return ACCURACY_FINE;
@@ -243,21 +266,20 @@
     }
 
     /**
-     * Indicates the desired maximum power level.  The level parameter
-     * must be one of NO_REQUIREMENT, POWER_LOW, POWER_MEDIUM, or
-     * POWER_HIGH.
+     * Indicates the desired maximum power requirement. The power requirement parameter may be
+     * {@link #NO_REQUIREMENT}, {@link #POWER_LOW}, {@link #POWER_MEDIUM}, or {@link #POWER_HIGH}.
      */
-    public void setPowerRequirement(int level) {
-        if (level < NO_REQUIREMENT || level > POWER_HIGH) {
-            throw new IllegalArgumentException("level=" + level);
-        }
-        mPowerRequirement = level;
+    public void setPowerRequirement(@PowerRequirement int powerRequirement) {
+        mPowerRequirement = Preconditions.checkArgumentInRange(powerRequirement, NO_REQUIREMENT,
+                POWER_HIGH, "powerRequirement");
     }
 
     /**
-     * Returns a constant indicating the desired power requirement.  The
-     * returned
+     * Returns a constant indicating the desired maximum power requirement.
+     *
+     * @see #setPowerRequirement(int)
      */
+    @PowerRequirement
     public int getPowerRequirement() {
         return mPowerRequirement;
     }
@@ -277,8 +299,8 @@
     }
 
     /**
-     * Indicates whether the provider must provide altitude information.
-     * Not all fixes are guaranteed to contain such information.
+     * Indicates whether the provider must provide altitude information. Not all fixes are
+     * guaranteed to contain such information.
      */
     public void setAltitudeRequired(boolean altitudeRequired) {
         mAltitudeRequired = altitudeRequired;
@@ -286,15 +308,16 @@
 
     /**
      * Returns whether the provider must provide altitude information.
-     * Not all fixes are guaranteed to contain such information.
+     *
+     * @see #setAltitudeRequired(boolean)
      */
     public boolean isAltitudeRequired() {
         return mAltitudeRequired;
     }
 
     /**
-     * Indicates whether the provider must provide speed information.
-     * Not all fixes are guaranteed to contain such information.
+     * Indicates whether the provider must provide speed information. Not all fixes are guaranteed
+     * to contain such information.
      */
     public void setSpeedRequired(boolean speedRequired) {
         mSpeedRequired = speedRequired;
@@ -302,15 +325,16 @@
 
     /**
      * Returns whether the provider must provide speed information.
-     * Not all fixes are guaranteed to contain such information.
+     *
+     * @see #setSpeedRequired(boolean)
      */
     public boolean isSpeedRequired() {
         return mSpeedRequired;
     }
 
     /**
-     * Indicates whether the provider must provide bearing information.
-     * Not all fixes are guaranteed to contain such information.
+     * Indicates whether the provider must provide bearing information. Not all fixes are guaranteed
+     * to contain such information.
      */
     public void setBearingRequired(boolean bearingRequired) {
         mBearingRequired = bearingRequired;
@@ -318,34 +342,36 @@
 
     /**
      * Returns whether the provider must provide bearing information.
-     * Not all fixes are guaranteed to contain such information.
+     *
+     * @see #setBearingRequired(boolean)
      */
     public boolean isBearingRequired() {
         return mBearingRequired;
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<Criteria> CREATOR =
-        new Parcelable.Creator<Criteria>() {
-        @Override
-        public Criteria createFromParcel(Parcel in) {
-            Criteria c = new Criteria();
-            c.mHorizontalAccuracy = in.readInt();
-            c.mVerticalAccuracy = in.readInt();
-            c.mSpeedAccuracy = in.readInt();
-            c.mBearingAccuracy = in.readInt();
-            c.mPowerRequirement = in.readInt();
-            c.mAltitudeRequired = in.readInt() != 0;
-            c.mBearingRequired = in.readInt() != 0;
-            c.mSpeedRequired = in.readInt() != 0;
-            c.mCostAllowed = in.readInt() != 0;
-            return c;
-        }
+    @NonNull
+    public static final Parcelable.Creator<Criteria> CREATOR =
+            new Parcelable.Creator<Criteria>() {
+                @Override
+                public Criteria createFromParcel(Parcel in) {
+                    Criteria c = new Criteria();
+                    c.mHorizontalAccuracy = in.readInt();
+                    c.mVerticalAccuracy = in.readInt();
+                    c.mSpeedAccuracy = in.readInt();
+                    c.mBearingAccuracy = in.readInt();
+                    c.mPowerRequirement = in.readInt();
+                    c.mAltitudeRequired = in.readInt() != 0;
+                    c.mBearingRequired = in.readInt() != 0;
+                    c.mSpeedRequired = in.readInt() != 0;
+                    c.mCostAllowed = in.readInt() != 0;
+                    return c;
+                }
 
-        @Override
-        public Criteria[] newArray(int size) {
-            return new Criteria[size];
-        }
-    };
+                @Override
+                public Criteria[] newArray(int size) {
+                    return new Criteria[size];
+                }
+            };
 
     @Override
     public int describeContents() {
@@ -365,42 +391,57 @@
         parcel.writeInt(mCostAllowed ? 1 : 0);
     }
 
-    private static String powerToString(int power) {
-        switch (power) {
-            case NO_REQUIREMENT:
-                return "NO_REQ";
-            case POWER_LOW:
-                return "LOW";
-            case POWER_MEDIUM:
-                return "MEDIUM";
-            case POWER_HIGH:
-                return "HIGH";
-            default:
-                return "???";
-        }
-    }
-
-    private static String accuracyToString(int accuracy) {
-        switch (accuracy) {
-            case NO_REQUIREMENT:
-                return "---";
-            case ACCURACY_HIGH:
-                return "HIGH";
-            case ACCURACY_MEDIUM:
-                return "MEDIUM";
-            case ACCURACY_LOW:
-                return "LOW";
-            default:
-                return "???";
-        }
-    }
-
     @Override
     public String toString() {
         StringBuilder s = new StringBuilder();
-        s.append("Criteria[power=").append(powerToString(mPowerRequirement));
-        s.append(" acc=").append(accuracyToString(mHorizontalAccuracy));
+        s.append("Criteria[");
+        s.append("power=").append(requirementToString(mPowerRequirement)).append(", ");
+        s.append("accuracy=").append(requirementToString(mHorizontalAccuracy));
+        if (mVerticalAccuracy != NO_REQUIREMENT) {
+            s.append(", verticalAccuracy=").append(requirementToString(mVerticalAccuracy));
+        }
+        if (mSpeedAccuracy != NO_REQUIREMENT) {
+            s.append(", speedAccuracy=").append(requirementToString(mSpeedAccuracy));
+        }
+        if (mBearingAccuracy != NO_REQUIREMENT) {
+            s.append(", bearingAccuracy=").append(requirementToString(mBearingAccuracy));
+        }
+        if (mAltitudeRequired || mBearingRequired || mSpeedRequired) {
+            s.append(", required=[");
+            if (mAltitudeRequired) {
+                s.append("altitude, ");
+            }
+            if (mBearingRequired) {
+                s.append("bearing, ");
+            }
+            if (mSpeedRequired) {
+                s.append("speed, ");
+            }
+            s.setLength(s.length() - 2);
+            s.append("]");
+        }
+        if (mCostAllowed) {
+            s.append(", costAllowed");
+        }
         s.append(']');
         return s.toString();
     }
+
+    private static String requirementToString(int power) {
+        switch (power) {
+            case NO_REQUIREMENT:
+                return "None";
+            //case ACCURACY_LOW:
+            case POWER_LOW:
+                return "Low";
+            //case ACCURACY_MEDIUM:
+            case POWER_MEDIUM:
+                return "Medium";
+            //case ACCURACY_HIGH:
+            case POWER_HIGH:
+                return "High";
+            default:
+                return "???";
+        }
+    }
 }
diff --git a/location/java/com/android/internal/location/ProviderProperties.java b/location/java/com/android/internal/location/ProviderProperties.java
index def96f0..68f9ec3 100644
--- a/location/java/com/android/internal/location/ProviderProperties.java
+++ b/location/java/com/android/internal/location/ProviderProperties.java
@@ -16,15 +16,36 @@
 
 package com.android.internal.location;
 
+import android.annotation.IntDef;
+import android.location.Criteria;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * A Parcelable containing (legacy) location provider properties.
  * This object is just used inside the framework and system services.
+ *
  * @hide
  */
 public final class ProviderProperties implements Parcelable {
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({Criteria.POWER_LOW, Criteria.POWER_MEDIUM, Criteria.POWER_HIGH})
+    public @interface PowerRequirement {
+    }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({Criteria.ACCURACY_FINE, Criteria.ACCURACY_COARSE})
+    public @interface Accuracy {
+    }
+
     /**
      * True if provider requires access to a
      * data network (e.g., the Internet), false otherwise.
@@ -79,58 +100,58 @@
 
     /**
      * Power requirement for this provider.
-     *
-     * @return the power requirement for this provider, as one of the
-     * constants Criteria.POWER_*.
      */
+    @PowerRequirement
     public final int mPowerRequirement;
 
     /**
      * Constant describing the horizontal accuracy returned
      * by this provider.
-     *
-     * @return the horizontal accuracy for this provider, as one of the
-     * constants Criteria.ACCURACY_COARSE or Criteria.ACCURACY_FINE
      */
+    @Accuracy
     public final int mAccuracy;
 
-    public ProviderProperties(boolean mRequiresNetwork,
-            boolean mRequiresSatellite, boolean mRequiresCell, boolean mHasMonetaryCost,
-            boolean mSupportsAltitude, boolean mSupportsSpeed, boolean mSupportsBearing,
-            int mPowerRequirement, int mAccuracy) {
-        this.mRequiresNetwork = mRequiresNetwork;
-        this.mRequiresSatellite = mRequiresSatellite;
-        this.mRequiresCell = mRequiresCell;
-        this.mHasMonetaryCost = mHasMonetaryCost;
-        this.mSupportsAltitude = mSupportsAltitude;
-        this.mSupportsSpeed = mSupportsSpeed;
-        this.mSupportsBearing = mSupportsBearing;
-        this.mPowerRequirement = mPowerRequirement;
-        this.mAccuracy = mAccuracy;
+    public ProviderProperties(boolean requiresNetwork, boolean requiresSatellite,
+            boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude,
+            boolean supportsSpeed, boolean supportsBearing, @PowerRequirement int powerRequirement,
+            @Accuracy int accuracy) {
+        mRequiresNetwork = requiresNetwork;
+        mRequiresSatellite = requiresSatellite;
+        mRequiresCell = requiresCell;
+        mHasMonetaryCost = hasMonetaryCost;
+        mSupportsAltitude = supportsAltitude;
+        mSupportsSpeed = supportsSpeed;
+        mSupportsBearing = supportsBearing;
+        mPowerRequirement = Preconditions.checkArgumentInRange(powerRequirement, Criteria.POWER_LOW,
+                Criteria.POWER_HIGH, "powerRequirement");
+        mAccuracy = Preconditions.checkArgumentInRange(accuracy, Criteria.ACCURACY_FINE,
+                Criteria.ACCURACY_COARSE, "accuracy");
     }
 
     public static final Parcelable.Creator<ProviderProperties> CREATOR =
             new Parcelable.Creator<ProviderProperties>() {
-        @Override
-        public ProviderProperties createFromParcel(Parcel in) {
-            boolean requiresNetwork = in.readInt() == 1;
-            boolean requiresSatellite = in.readInt() == 1;
-            boolean requiresCell = in.readInt() == 1;
-            boolean hasMonetaryCost = in.readInt() == 1;
-            boolean supportsAltitude = in.readInt() == 1;
-            boolean supportsSpeed = in.readInt() == 1;
-            boolean supportsBearing = in.readInt() == 1;
-            int powerRequirement = in.readInt();
-            int accuracy = in.readInt();
-            return new ProviderProperties(requiresNetwork, requiresSatellite,
-                    requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, supportsBearing,
-                    powerRequirement, accuracy);
-        }
-        @Override
-        public ProviderProperties[] newArray(int size) {
-            return new ProviderProperties[size];
-        }
-    };
+                @Override
+                public ProviderProperties createFromParcel(Parcel in) {
+                    boolean requiresNetwork = in.readInt() == 1;
+                    boolean requiresSatellite = in.readInt() == 1;
+                    boolean requiresCell = in.readInt() == 1;
+                    boolean hasMonetaryCost = in.readInt() == 1;
+                    boolean supportsAltitude = in.readInt() == 1;
+                    boolean supportsSpeed = in.readInt() == 1;
+                    boolean supportsBearing = in.readInt() == 1;
+                    int powerRequirement = in.readInt();
+                    int accuracy = in.readInt();
+                    return new ProviderProperties(requiresNetwork, requiresSatellite,
+                            requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed,
+                            supportsBearing,
+                            powerRequirement, accuracy);
+                }
+
+                @Override
+                public ProviderProperties[] newArray(int size) {
+                    return new ProviderProperties[size];
+                }
+            };
 
     @Override
     public int describeContents() {
@@ -149,4 +170,67 @@
         parcel.writeInt(mPowerRequirement);
         parcel.writeInt(mAccuracy);
     }
+
+    @Override
+    public String toString() {
+        StringBuilder b = new StringBuilder("ProviderProperties[");
+        b.append("power=").append(powerToString(mPowerRequirement)).append(", ");
+        b.append("accuracy=").append(accuracyToString(mAccuracy));
+        if (mRequiresNetwork || mRequiresSatellite || mRequiresCell) {
+            b.append(", requires=");
+            if (mRequiresNetwork) {
+                b.append("network,");
+            }
+            if (mRequiresSatellite) {
+                b.append("satellite,");
+            }
+            if (mRequiresCell) {
+                b.append("cell,");
+            }
+            b.setLength(b.length() - 1);
+        }
+        if (mHasMonetaryCost) {
+            b.append(", hasMonetaryCost");
+        }
+        if (mSupportsBearing || mSupportsSpeed || mSupportsAltitude) {
+            b.append(", supports=[");
+            if (mSupportsBearing) {
+                b.append("bearing, ");
+            }
+            if (mSupportsSpeed) {
+                b.append("speed, ");
+            }
+            if (mSupportsAltitude) {
+                b.append("altitude, ");
+            }
+            b.setLength(b.length() - 2);
+            b.append("]");
+        }
+        b.append("]");
+        return b.toString();
+    }
+
+    private static String powerToString(@PowerRequirement int power) {
+        switch (power) {
+            case Criteria.POWER_LOW:
+                return "Low";
+            case Criteria.POWER_MEDIUM:
+                return "Medium";
+            case Criteria.POWER_HIGH:
+                return "High";
+            default:
+                return "???";
+        }
+    }
+
+    private static String accuracyToString(@Accuracy int accuracy) {
+        switch (accuracy) {
+            case Criteria.ACCURACY_COARSE:
+                return "Coarse";
+            case Criteria.ACCURACY_FINE:
+                return "Fine";
+            default:
+                return "???";
+        }
+    }
 }
diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java
index c23f499..8d8df45 100644
--- a/location/java/com/android/internal/location/ProviderRequest.java
+++ b/location/java/com/android/internal/location/ProviderRequest.java
@@ -20,33 +20,42 @@
 import android.location.LocationRequest;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.WorkSource;
 import android.util.TimeUtils;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /** @hide */
 public final class ProviderRequest implements Parcelable {
+
+    public static final ProviderRequest EMPTY_REQUEST = new ProviderRequest(false, Long.MAX_VALUE,
+            false, false,
+            Collections.emptyList(), new WorkSource());
+
     /** Location reporting is requested (true) */
     @UnsupportedAppUsage
-    public boolean reportLocation = false;
+    public final boolean reportLocation;
 
     /** The smallest requested interval */
     @UnsupportedAppUsage
-    public long interval = Long.MAX_VALUE;
+    public final long interval;
+
+    /**
+     * Whether provider shall make stronger than normal tradeoffs to substantially restrict power
+     * use.
+     */
+    public final boolean lowPowerMode;
 
     /**
      * When this flag is true, providers should ignore all location settings, user consents, power
      * restrictions or any other restricting factors and always satisfy this request to the best of
      * their ability. This flag should only be used in event of an emergency.
      */
-    public boolean locationSettingsIgnored = false;
-
-    /**
-     * Whether provider shall make stronger than normal tradeoffs to substantially restrict power
-     * use.
-     */
-    public boolean lowPowerMode = false;
+    public final boolean locationSettingsIgnored;
 
     /**
      * A more detailed set of requests.
@@ -56,26 +65,37 @@
      * low power fast interval request.
      */
     @UnsupportedAppUsage
-    public final List<LocationRequest> locationRequests = new ArrayList<>();
+    public final List<LocationRequest> locationRequests;
 
-    @UnsupportedAppUsage
-    public ProviderRequest() {
+    public final WorkSource workSource;
+
+    private ProviderRequest(boolean reportLocation, long interval, boolean lowPowerMode,
+            boolean locationSettingsIgnored, List<LocationRequest> locationRequests,
+            WorkSource workSource) {
+        this.reportLocation = reportLocation;
+        this.interval = interval;
+        this.lowPowerMode = lowPowerMode;
+        this.locationSettingsIgnored = locationSettingsIgnored;
+        this.locationRequests = Preconditions.checkNotNull(locationRequests);
+        this.workSource = Preconditions.checkNotNull(workSource);
     }
 
     public static final Parcelable.Creator<ProviderRequest> CREATOR =
             new Parcelable.Creator<ProviderRequest>() {
                 @Override
                 public ProviderRequest createFromParcel(Parcel in) {
-                    ProviderRequest request = new ProviderRequest();
-                    request.reportLocation = in.readInt() == 1;
-                    request.interval = in.readLong();
-                    request.lowPowerMode = in.readBoolean();
-                    request.locationSettingsIgnored = in.readBoolean();
+                    boolean reportLocation = in.readInt() == 1;
+                    long interval = in.readLong();
+                    boolean lowPowerMode = in.readBoolean();
+                    boolean locationSettingsIgnored = in.readBoolean();
                     int count = in.readInt();
+                    ArrayList<LocationRequest> locationRequests = new ArrayList<>(count);
                     for (int i = 0; i < count; i++) {
-                        request.locationRequests.add(LocationRequest.CREATOR.createFromParcel(in));
+                        locationRequests.add(LocationRequest.CREATOR.createFromParcel(in));
                     }
-                    return request;
+                    WorkSource workSource = in.readParcelable(null);
+                    return new ProviderRequest(reportLocation, interval, lowPowerMode,
+                            locationSettingsIgnored, locationRequests, workSource);
                 }
 
                 @Override
@@ -106,14 +126,13 @@
         StringBuilder s = new StringBuilder();
         s.append("ProviderRequest[");
         if (reportLocation) {
-            s.append("ON");
-            s.append(" interval=");
+            s.append("interval=");
             TimeUtils.formatDuration(interval, s);
             if (lowPowerMode) {
-                s.append(" lowPowerMode");
+                s.append(", lowPowerMode");
             }
             if (locationSettingsIgnored) {
-                s.append(" locationSettingsIgnored");
+                s.append(", locationSettingsIgnored");
             }
         } else {
             s.append("OFF");
@@ -121,4 +140,67 @@
         s.append(']');
         return s.toString();
     }
+
+    /**
+     * A Builder for {@link ProviderRequest}s.
+     */
+    public static class Builder {
+        private long mInterval = Long.MAX_VALUE;
+        private boolean mLowPowerMode;
+        private boolean mLocationSettingsIgnored;
+        private List<LocationRequest> mLocationRequests = Collections.emptyList();
+        private WorkSource mWorkSource = new WorkSource();
+
+        public long getInterval() {
+            return mInterval;
+        }
+
+        public void setInterval(long interval) {
+            this.mInterval = interval;
+        }
+
+        public boolean isLowPowerMode() {
+            return mLowPowerMode;
+        }
+
+        public void setLowPowerMode(boolean lowPowerMode) {
+            this.mLowPowerMode = lowPowerMode;
+        }
+
+        public boolean isLocationSettingsIgnored() {
+            return mLocationSettingsIgnored;
+        }
+
+        public void setLocationSettingsIgnored(boolean locationSettingsIgnored) {
+            this.mLocationSettingsIgnored = locationSettingsIgnored;
+        }
+
+        public List<LocationRequest> getLocationRequests() {
+            return mLocationRequests;
+        }
+
+        public void setLocationRequests(List<LocationRequest> locationRequests) {
+            this.mLocationRequests = Preconditions.checkNotNull(locationRequests);
+        }
+
+        public WorkSource getWorkSource() {
+            return mWorkSource;
+        }
+
+        public void setWorkSource(WorkSource workSource) {
+            mWorkSource = Preconditions.checkNotNull(workSource);
+        }
+
+        /**
+         * Builds a ProviderRequest object with the set information.
+         */
+        public ProviderRequest build() {
+            if (mInterval == Long.MAX_VALUE) {
+                return EMPTY_REQUEST;
+            } else {
+                return new ProviderRequest(true, mInterval, mLowPowerMode,
+                        mLocationSettingsIgnored, mLocationRequests, mWorkSource);
+            }
+        }
+    }
 }
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 8293b5f..cb132f5 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.util.SparseIntArray;
 
 import java.lang.annotation.Retention;
@@ -127,6 +128,23 @@
      * A device type describing a Hearing Aid.
      */
     public static final int TYPE_HEARING_AID   = 23;
+    /**
+     * A device type describing the speaker system (i.e. a mono speaker or stereo speakers) built
+     * in a device, that is specifically tuned for outputting sounds like notifications and alarms
+     * (i.e. sounds the user couldn't necessarily anticipate).
+     * <p>Note that this physical audio device may be the same as {@link #TYPE_BUILTIN_SPEAKER}
+     * but is driven differently to safely accommodate the different use case.</p>
+     */
+    public static final int TYPE_BUILTIN_SPEAKER_SAFE = 24;
+    /**
+     * @hide
+     * A device type for rerouting audio within the Android framework between mixes and
+     * system applications. Typically created when using
+     * {@link android.media.audiopolicy.AudioPolicy} for mixes created with the
+     * {@link android.media.audiopolicy.AudioMix#ROUTE_FLAG_RENDER} flag.
+     */
+    @SystemApi
+    public static final int TYPE_REMOTE_SUBMIX = 25;
 
     /** @hide */
     @IntDef(flag = false, prefix = "TYPE", value = {
@@ -228,6 +246,8 @@
             case TYPE_IP:
             case TYPE_BUS:
             case TYPE_HEARING_AID:
+            case TYPE_BUILTIN_SPEAKER_SAFE:
+            case TYPE_REMOTE_SUBMIX:
                 return true;
             default:
                 return false;
@@ -253,6 +273,7 @@
             case TYPE_LINE_DIGITAL:
             case TYPE_IP:
             case TYPE_BUS:
+            case TYPE_REMOTE_SUBMIX:
                 return true;
             default:
                 return false;
@@ -449,6 +470,9 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_IP, TYPE_IP);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BUS, TYPE_BUS);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_HEARING_AID, TYPE_HEARING_AID);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_SPEAKER_SAFE,
+                TYPE_BUILTIN_SPEAKER_SAFE);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
 
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO);
@@ -468,10 +492,7 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, TYPE_BLUETOOTH_A2DP);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_IP, TYPE_IP);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUS, TYPE_BUS);
-
-        // not covered here, legacy
-        //AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
-        //AudioSystem.DEVICE_IN_REMOTE_SUBMIX
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
 
         // privileges mapping to output device
         EXT_TO_INT_DEVICE_MAPPING = new SparseIntArray();
@@ -498,6 +519,9 @@
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_IP, AudioSystem.DEVICE_OUT_IP);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUS, AudioSystem.DEVICE_OUT_BUS);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_HEARING_AID, AudioSystem.DEVICE_OUT_HEARING_AID);
+        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUILTIN_SPEAKER_SAFE,
+                AudioSystem.DEVICE_OUT_SPEAKER_SAFE);
+        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_OUT_REMOTE_SUBMIX);
     }
 }
 
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e410882..1b870e8 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4331,6 +4331,26 @@
         }
     }
 
+    /**
+     * @hide
+     * Get the audio devices that would be used for the routing of the given audio attributes.
+     * @param attributes the {@link AudioAttributes} for which the routing is being queried
+     * @return an empty list if there was an issue with the request, a list of audio devices
+     *   otherwise (typically one device, except for duplicated paths).
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public @NonNull List<AudioDeviceAddress> getDevicesForAttributes(
+            @NonNull AudioAttributes attributes) {
+        Objects.requireNonNull(attributes);
+        final IAudioService service = getService();
+        try {
+            return service.getDevicesForAttributes(attributes);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
      /**
      * Indicate wired accessory connection state change.
      * @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx)
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index e584add..fe57e71 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -33,6 +33,7 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /* IF YOU CHANGE ANY OF THE CONSTANTS IN THIS FILE, DO NOT FORGET
@@ -1077,6 +1078,41 @@
     @UnsupportedAppUsage
     public static native int getDevicesForStream(int stream);
 
+    /**
+     * Do not use directly, see {@link AudioManager#getDevicesForAttributes(AudioAttributes)}
+     * Get the audio devices that would be used for the routing of the given audio attributes.
+     * @param attributes the {@link AudioAttributes} for which the routing is being queried
+     * @return an empty list if there was an issue with the request, a list of audio devices
+     *   otherwise (typically one device, except for duplicated paths).
+     */
+    public static @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes(
+            @NonNull AudioAttributes attributes) {
+        Objects.requireNonNull(attributes);
+        final AudioDeviceAddress[] devices = new AudioDeviceAddress[MAX_DEVICE_ROUTING];
+        final int res = getDevicesForAttributes(attributes, devices);
+        final ArrayList<AudioDeviceAddress> routeDevices = new ArrayList<>();
+        if (res != SUCCESS) {
+            Log.e(TAG, "error " + res + " in getDevicesForAttributes for " + attributes);
+            return routeDevices;
+        }
+
+        for (AudioDeviceAddress device : devices) {
+            if (device != null) {
+                routeDevices.add(device);
+            }
+        }
+        return routeDevices;
+    }
+
+    /**
+     * Maximum number of audio devices a track is ever routed to, determines the size of the
+     * array passed to {@link #getDevicesForAttributes(AudioAttributes, AudioDeviceAddress[])}
+     */
+    private static final int MAX_DEVICE_ROUTING = 4;
+
+    private static native int getDevicesForAttributes(@NonNull AudioAttributes aa,
+                                                      @NonNull AudioDeviceAddress[] devices);
+
     /** @hide returns true if master mono is enabled. */
     public static native boolean getMasterMono();
     /** @hide enables or disables the master mono mode. */
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index ad7335e..8e8385d 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -272,6 +272,8 @@
 
     AudioDeviceAddress getPreferredDeviceForStrategy(in int strategy);
 
+    List<AudioDeviceAddress> getDevicesForAttributes(in AudioAttributes attributes);
+
     // WARNING: read warning at top of file, new methods that need to be used by native
     // code via IAudioManager.h need to be added to the top section.
 }
diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl
index 02a3816..5dd0b1c 100644
--- a/media/java/android/media/IMediaRoute2Provider.aidl
+++ b/media/java/android/media/IMediaRoute2Provider.aidl
@@ -25,12 +25,12 @@
 oneway interface IMediaRoute2Provider {
     void setClient(IMediaRoute2ProviderClient client);
     void requestCreateSession(String packageName, String routeId,
-            String controlCategory, long requestId);
-    void releaseSession(int sessionId);
+            String routeFeature, long requestId);
+    void releaseSession(String sessionId);
 
-    void selectRoute(int sessionId, String routeId);
-    void deselectRoute(int sessionId, String routeId);
-    void transferToRoute(int sessionId, String routeId);
+    void selectRoute(String sessionId, String routeId);
+    void deselectRoute(String sessionId, String routeId);
+    void transferToRoute(String sessionId, String routeId);
 
     void notifyControlRequestSent(String id, in Intent request);
     void requestSetVolume(String id, int volume);
diff --git a/media/java/android/media/IMediaRoute2ProviderClient.aidl b/media/java/android/media/IMediaRoute2ProviderClient.aidl
index bcb2336..e8abfdc 100644
--- a/media/java/android/media/IMediaRoute2ProviderClient.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderClient.aidl
@@ -18,7 +18,7 @@
 
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2Info;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
 import android.os.Bundle;
 
 /**
@@ -26,7 +26,7 @@
  */
 oneway interface IMediaRoute2ProviderClient {
     void updateState(in MediaRoute2ProviderInfo providerInfo,
-            in List<RouteSessionInfo> sessionInfos);
-    void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, long requestId);
-    void notifySessionInfoChanged(in RouteSessionInfo sessionInfo);
+            in List<RoutingSessionInfo> sessionInfos);
+    void notifySessionCreated(in @nullable RoutingSessionInfo sessionInfo, long requestId);
+    void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo);
 }
diff --git a/media/java/android/media/IMediaRouter2Client.aidl b/media/java/android/media/IMediaRouter2Client.aidl
index f90c7c5..bc7ebea 100644
--- a/media/java/android/media/IMediaRouter2Client.aidl
+++ b/media/java/android/media/IMediaRouter2Client.aidl
@@ -17,7 +17,7 @@
 package android.media;
 
 import android.media.MediaRoute2Info;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
 import android.os.Bundle;
 
 /**
@@ -28,7 +28,7 @@
     void notifyRoutesAdded(in List<MediaRoute2Info> routes);
     void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
     void notifyRoutesChanged(in List<MediaRoute2Info> routes);
-    void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, int requestId);
-    void notifySessionInfoChanged(in RouteSessionInfo sessionInfo);
-    void notifySessionReleased(in RouteSessionInfo sessionInfo);
+    void notifySessionCreated(in @nullable RoutingSessionInfo sessionInfo, int requestId);
+    void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo);
+    void notifySessionReleased(in RoutingSessionInfo sessionInfo);
 }
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index b7cb705..e9add17 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -24,7 +24,7 @@
  */
 oneway interface IMediaRouter2Manager {
     void notifyRouteSelected(String packageName, in MediaRoute2Info route);
-    void notifyControlCategoriesChanged(String packageName, in List<String> categories);
+    void notifyPreferredFeaturesChanged(String packageName, in List<String> preferredFeatures);
     void notifyRoutesAdded(in List<MediaRoute2Info> routes);
     void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
     void notifyRoutesChanged(in List<MediaRoute2Info> routes);
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index e5b62ff..3cdaa07 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -22,7 +22,8 @@
 import android.media.IMediaRouterClient;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouterClientState;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
 
 /**
  * {@hide}
@@ -51,8 +52,8 @@
     void requestUpdateVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int direction);
 
     void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route,
-            String controlCategory, int requestId);
-    void setControlCategories(IMediaRouter2Client client, in List<String> categories);
+            String routeFeature, int requestId);
+    void setDiscoveryRequest2(IMediaRouter2Client client, in RouteDiscoveryPreference preference);
     void selectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
     void deselectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
     void transferToRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
@@ -69,5 +70,5 @@
     void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
             in MediaRoute2Info route, int direction);
 
-    List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager);
+    List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager);
 }
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 7fca03c..4cd581b 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import android.annotation.IntDef;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -354,8 +355,8 @@
      *         is less than or equal to 0.
      * @see {@link #getScaledFrameAtTime(long, int, int, int, BitmapParams)}
      */
-    public @Nullable Bitmap getScaledFrameAtTime(
-            long timeUs, @Option int option, int dstWidth, int dstHeight) {
+    public @Nullable Bitmap getScaledFrameAtTime(long timeUs, @Option int option,
+            @IntRange(from=1) int dstWidth, @IntRange(from=1) int dstHeight) {
         validate(option, dstWidth, dstHeight);
         return _getFrameAtTime(timeUs, option, dstWidth, dstHeight, null);
     }
@@ -400,7 +401,8 @@
      * @see {@link #getScaledFrameAtTime(long, int, int, int)}
      */
     public @Nullable Bitmap getScaledFrameAtTime(long timeUs, @Option int option,
-            int dstWidth, int dstHeight, @NonNull BitmapParams params) {
+            @IntRange(from=1) int dstWidth, @IntRange(from=1) int dstHeight,
+            @NonNull BitmapParams params) {
         validate(option, dstWidth, dstHeight);
         return _getFrameAtTime(timeUs, option, dstWidth, dstHeight, params);
     }
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 506d616..021e23e 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -16,6 +16,8 @@
 
 package android.media;
 
+import static android.media.MediaRouter2Utils.toUniqueId;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -34,7 +36,6 @@
 
 /**
  * Describes the properties of a route.
- * @hide
  */
 public final class MediaRoute2Info implements Parcelable {
     @NonNull
@@ -54,7 +55,7 @@
     @IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
             CONNECTION_STATE_CONNECTED})
     @Retention(RetentionPolicy.SOURCE)
-    private @interface ConnectionState {}
+    public @interface ConnectionState {}
 
     /**
      * The default connection state indicating the route is disconnected.
@@ -83,28 +84,29 @@
      * controlled from this object. An example of fixed playback volume is a remote player,
      * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
      * than attenuate at the source.
+     *
      * @see #getVolumeHandling()
      */
     public static final int PLAYBACK_VOLUME_FIXED = 0;
     /**
      * Playback information indicating the playback volume is variable and can be controlled
      * from this object.
+     *
      * @see #getVolumeHandling()
      */
     public static final int PLAYBACK_VOLUME_VARIABLE = 1;
 
     /** @hide */
     @IntDef({
-            DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_TV,
-            DEVICE_TYPE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
+            DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_REMOTE_TV,
+            DEVICE_TYPE_REMOTE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
     @Retention(RetentionPolicy.SOURCE)
-    private @interface DeviceType {}
+    public @interface DeviceType {}
 
     /**
      * The default receiver device type of the route indicating the type is unknown.
      *
      * @see #getDeviceType
-     * @hide
      */
     public static final int DEVICE_TYPE_UNKNOWN = 0;
 
@@ -114,7 +116,7 @@
      *
      * @see #getDeviceType
      */
-    public static final int DEVICE_TYPE_TV = 1;
+    public static final int DEVICE_TYPE_REMOTE_TV = 1;
 
     /**
      * A receiver device type of the route indicating the presentation of the media is happening
@@ -122,14 +124,13 @@
      *
      * @see #getDeviceType
      */
-    public static final int DEVICE_TYPE_SPEAKER = 2;
+    public static final int DEVICE_TYPE_REMOTE_SPEAKER = 2;
 
     /**
      * A receiver device type of the route indicating the presentation of the media is happening
      * on a bluetooth device such as a bluetooth speaker.
      *
      * @see #getDeviceType
-     * @hide
      */
     public static final int DEVICE_TYPE_BLUETOOTH = 3;
 
@@ -148,7 +149,7 @@
     @Nullable
     final String mClientPackageName;
     @NonNull
-    final List<String> mSupportedCategories;
+    final List<String> mFeatures;
     final int mVolume;
     final int mVolumeMax;
     final int mVolumeHandling;
@@ -156,8 +157,6 @@
     @Nullable
     final Bundle mExtras;
 
-    private final String mUniqueId;
-
     MediaRoute2Info(@NonNull Builder builder) {
         mId = builder.mId;
         mProviderId = builder.mProviderId;
@@ -166,13 +165,12 @@
         mConnectionState = builder.mConnectionState;
         mIconUri = builder.mIconUri;
         mClientPackageName = builder.mClientPackageName;
-        mSupportedCategories = builder.mSupportedCategories;
+        mFeatures = builder.mFeatures;
         mVolume = builder.mVolume;
         mVolumeMax = builder.mVolumeMax;
         mVolumeHandling = builder.mVolumeHandling;
         mDeviceType = builder.mDeviceType;
         mExtras = builder.mExtras;
-        mUniqueId = createUniqueId();
     }
 
     MediaRoute2Info(@NonNull Parcel in) {
@@ -183,25 +181,12 @@
         mConnectionState = in.readInt();
         mIconUri = in.readParcelable(null);
         mClientPackageName = in.readString();
-        mSupportedCategories = in.createStringArrayList();
+        mFeatures = in.createStringArrayList();
         mVolume = in.readInt();
         mVolumeMax = in.readInt();
         mVolumeHandling = in.readInt();
         mDeviceType = in.readInt();
         mExtras = in.readBundle();
-        mUniqueId = createUniqueId();
-    }
-
-    private String createUniqueId() {
-        String uniqueId = null;
-        if (mProviderId != null) {
-            uniqueId = toUniqueId(mProviderId, mId);
-        }
-        return uniqueId;
-    }
-
-    static String toUniqueId(String providerId, String routeId) {
-        return providerId + ":" + routeId;
     }
 
     /**
@@ -235,7 +220,7 @@
                 && (mConnectionState == other.mConnectionState)
                 && Objects.equals(mIconUri, other.mIconUri)
                 && Objects.equals(mClientPackageName, other.mClientPackageName)
-                && Objects.equals(mSupportedCategories, other.mSupportedCategories)
+                && Objects.equals(mFeatures, other.mFeatures)
                 && (mVolume == other.mVolume)
                 && (mVolumeMax == other.mVolumeMax)
                 && (mVolumeHandling == other.mVolumeHandling)
@@ -247,29 +232,34 @@
     @Override
     public int hashCode() {
         return Objects.hash(mId, mName, mDescription, mConnectionState, mIconUri,
-                mSupportedCategories, mVolume, mVolumeMax, mVolumeHandling, mDeviceType);
+                mFeatures, mVolume, mVolumeMax, mVolumeHandling, mDeviceType);
     }
 
     /**
-     * Gets the id of the route.
-     * Use {@link #getUniqueId()} if you need a unique identifier.
+     * Gets the id of the route. The routes which are given by {@link MediaRouter2} will have
+     * unique IDs.
+     * <p>
+     * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method
+     * can be different from what was set in {@link MediaRoute2ProviderService}.
      *
-     * @see #getUniqueId()
+     * @see Builder#Builder(String, CharSequence)
      */
     @NonNull
     public String getId() {
-        return mId;
+        if (mProviderId != null) {
+            return toUniqueId(mProviderId, mId);
+        } else {
+            return mId;
+        }
     }
 
     /**
-     * Gets the unique id of the route. A route obtained from
-     * {@link com.android.server.media.MediaRouterService} always has a unique id.
-     *
-     * @return unique id of the route or null if it has no unique id.
+     * Gets the original id set by {@link Builder#Builder(String, CharSequence)}.
+     * @hide
      */
-    @Nullable
-    public String getUniqueId() {
-        return mUniqueId;
+    @NonNull
+    public String getOriginalId() {
+        return mId;
     }
 
     /**
@@ -331,16 +321,16 @@
      * Gets the supported categories of the route.
      */
     @NonNull
-    public List<String> getSupportedCategories() {
-        return mSupportedCategories;
+    public List<String> getFeatures() {
+        return mFeatures;
     }
 
-    //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}.
+     * {@link #DEVICE_TYPE_REMOTE_TV}, {@link #DEVICE_TYPE_REMOTE_SPEAKER},
+     * {@link #DEVICE_TYPE_BLUETOOTH}.
      */
     @DeviceType
     public int getDeviceType() {
@@ -376,32 +366,15 @@
     }
 
     /**
-     * Returns if the route supports the specified control category
+     * Returns if the route has at least one of the specified route features.
      *
-     * @param controlCategory control category to consider
-     * @return true if the route supports at the category
+     * @param features the list of route features to consider
+     * @return true if the route has at least one feature in the list
      */
-    public boolean supportsControlCategory(@NonNull String controlCategory) {
-        Objects.requireNonNull(controlCategory, "control category must not be null");
-        for (String supportedCategory : getSupportedCategories()) {
-            if (TextUtils.equals(controlCategory, supportedCategory)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    //TODO: Move this if we re-define control category / selector things.
-    /**
-     * Returns if the route supports at least one of the specified control categories
-     *
-     * @param controlCategories the list of control categories to consider
-     * @return true if the route supports at least one category
-     */
-    public boolean supportsControlCategories(@NonNull Collection<String> controlCategories) {
-        Objects.requireNonNull(controlCategories, "control categories must not be null");
-        for (String controlCategory : controlCategories) {
-            if (supportsControlCategory(controlCategory)) {
+    public boolean hasAnyFeatures(@NonNull Collection<String> features) {
+        Objects.requireNonNull(features, "features must not be null");
+        for (String feature : features) {
+            if (getFeatures().contains(feature)) {
                 return true;
             }
         }
@@ -422,7 +395,7 @@
         dest.writeInt(mConnectionState);
         dest.writeParcelable(mIconUri, flags);
         dest.writeString(mClientPackageName);
-        dest.writeStringList(mSupportedCategories);
+        dest.writeStringList(mFeatures);
         dest.writeInt(mVolume);
         dest.writeInt(mVolumeMax);
         dest.writeInt(mVolumeHandling);
@@ -452,15 +425,15 @@
      * Builder for {@link MediaRoute2Info media route info}.
      */
     public static final class Builder {
-        String mId;
+        final String mId;
         String mProviderId;
-        CharSequence mName;
+        final CharSequence mName;
         CharSequence mDescription;
         @ConnectionState
         int mConnectionState;
         Uri mIconUri;
         String mClientPackageName;
-        List<String> mSupportedCategories;
+        List<String> mFeatures;
         int mVolume;
         int mVolumeMax;
         int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
@@ -468,27 +441,43 @@
         int mDeviceType = DEVICE_TYPE_UNKNOWN;
         Bundle mExtras;
 
+        /**
+         * Constructor for builder to create {@link MediaRoute2Info}.
+         * <p>
+         * In order to ensure ID uniqueness, the {@link MediaRoute2Info#getId() ID} of a route info
+         * obtained from {@link MediaRouter2} can be different from what was set in
+         * {@link MediaRoute2ProviderService}.
+         * </p>
+         * @param id
+         * @param name
+         */
         public Builder(@NonNull String id, @NonNull CharSequence name) {
-            setId(id);
-            setName(name);
-            mSupportedCategories = new ArrayList<>();
+            if (TextUtils.isEmpty(id)) {
+                throw new IllegalArgumentException("id must not be empty");
+            }
+            if (TextUtils.isEmpty(name)) {
+                throw new IllegalArgumentException("name must not be empty");
+            }
+            mId = id;
+            mName = name;
+            mFeatures = new ArrayList<>();
         }
 
         public Builder(@NonNull MediaRoute2Info routeInfo) {
             if (routeInfo == null) {
                 throw new IllegalArgumentException("route info must not be null");
             }
+            mId = routeInfo.mId;
+            mName = routeInfo.mName;
 
-            setId(routeInfo.mId);
             if (!TextUtils.isEmpty(routeInfo.mProviderId)) {
                 setProviderId(routeInfo.mProviderId);
             }
-            setName(routeInfo.mName);
             mDescription = routeInfo.mDescription;
             mConnectionState = routeInfo.mConnectionState;
             mIconUri = routeInfo.mIconUri;
             setClientPackageName(routeInfo.mClientPackageName);
-            setSupportedCategories(routeInfo.mSupportedCategories);
+            mFeatures = new ArrayList<>(routeInfo.mFeatures);
             setVolume(routeInfo.mVolume);
             setVolumeMax(routeInfo.mVolumeMax);
             setVolumeHandling(routeInfo.mVolumeHandling);
@@ -499,18 +488,6 @@
         }
 
         /**
-         * Sets the unique id of the route.
-         */
-        @NonNull
-        public Builder setId(@NonNull String id) {
-            if (TextUtils.isEmpty(id)) {
-                throw new IllegalArgumentException("id must not be null or empty");
-            }
-            mId = id;
-            return this;
-        }
-
-        /**
          * Sets the provider id of the route.
          * @hide
          */
@@ -524,20 +501,10 @@
         }
 
         /**
-         * Sets the user-visible name of the route.
-         */
-        @NonNull
-        public Builder setName(@NonNull CharSequence name) {
-            Objects.requireNonNull(name, "name must not be null");
-            mName = name;
-            return this;
-        }
-
-        /**
          * Sets the user-visible description of the route.
          */
         @NonNull
-        public Builder setDescription(@Nullable String description) {
+        public Builder setDescription(@Nullable CharSequence description) {
             mDescription = description;
             return this;
         }
@@ -585,35 +552,35 @@
         }
 
         /**
-         * Sets the supported categories of the route.
+         * Clears the features of the route.
          */
         @NonNull
-        public Builder setSupportedCategories(@NonNull Collection<String> categories) {
-            mSupportedCategories = new ArrayList<>();
-            return addSupportedCategories(categories);
+        public Builder clearFeatures() {
+            mFeatures = new ArrayList<>();
+            return this;
         }
 
         /**
-         * Adds supported categories for the route.
+         * Adds features for the route.
          */
         @NonNull
-        public Builder addSupportedCategories(@NonNull Collection<String> categories) {
-            Objects.requireNonNull(categories, "categories must not be null");
-            for (String category: categories) {
-                addSupportedCategory(category);
+        public Builder addFeatures(@NonNull Collection<String> features) {
+            Objects.requireNonNull(features, "features must not be null");
+            for (String feature : features) {
+                addFeature(feature);
             }
             return this;
         }
 
         /**
-         * Add a supported category for the route.
+         * Adds a feature for the route.
          */
         @NonNull
-        public Builder addSupportedCategory(@NonNull String category) {
-            if (TextUtils.isEmpty(category)) {
-                throw new IllegalArgumentException("category must not be null or empty");
+        public Builder addFeature(@NonNull String feature) {
+            if (TextUtils.isEmpty(feature)) {
+                throw new IllegalArgumentException("feature must not be null or empty");
             }
-            mSupportedCategories.add(category);
+            mFeatures.add(feature);
             return this;
         }
 
@@ -658,7 +625,7 @@
          */
         @NonNull
         public Builder setExtras(@Nullable Bundle extras) {
-            mExtras = extras;
+            mExtras = new Bundle(extras);
             return this;
         }
 
diff --git a/media/java/android/media/MediaRoute2ProviderInfo.java b/media/java/android/media/MediaRoute2ProviderInfo.java
index 7078d4a..c9a2ec7 100644
--- a/media/java/android/media/MediaRoute2ProviderInfo.java
+++ b/media/java/android/media/MediaRoute2ProviderInfo.java
@@ -25,6 +25,7 @@
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -92,6 +93,9 @@
 
     /**
      * Gets the route for the given route id or null if no matching route exists.
+     * Please note that id should be original id.
+     *
+     * @see MediaRoute2Info#getOriginalId()
      */
     @Nullable
     public MediaRoute2Info getRoute(@NonNull String routeId) {
@@ -161,14 +165,17 @@
                 return this;
             }
             mUniqueId = uniqueId;
-            final int count = mRoutes.size();
-            for (int i = 0; i < count; i++) {
-                MediaRoute2Info route = mRoutes.valueAt(i);
-                mRoutes.setValueAt(i, new MediaRoute2Info.Builder(route)
+
+            final ArrayMap<String, MediaRoute2Info> newRoutes = new ArrayMap<>();
+            for (Map.Entry<String, MediaRoute2Info> entry : mRoutes.entrySet()) {
+                MediaRoute2Info routeWithProviderId = new MediaRoute2Info.Builder(entry.getValue())
                         .setProviderId(mUniqueId)
-                        .build());
+                        .build();
+                newRoutes.put(routeWithProviderId.getOriginalId(), routeWithProviderId);
             }
 
+            mRoutes.clear();
+            mRoutes.putAll(newRoutes);
             return this;
         }
 
@@ -179,14 +186,14 @@
         public Builder addRoute(@NonNull MediaRoute2Info route) {
             Objects.requireNonNull(route, "route must not be null");
 
-            if (mRoutes.containsValue(route)) {
-                throw new IllegalArgumentException("route descriptor already added");
+            if (mRoutes.containsKey(route.getOriginalId())) {
+                throw new IllegalArgumentException("A route with the same id is already added");
             }
             if (mUniqueId != null) {
-                mRoutes.put(route.getId(),
+                mRoutes.put(route.getOriginalId(),
                         new MediaRoute2Info.Builder(route).setProviderId(mUniqueId).build());
             } else {
-                mRoutes.put(route.getId(), route);
+                mRoutes.put(route.getOriginalId(), route);
             }
             return this;
         }
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 99bd1dc..1cd5dfa 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -29,18 +29,29 @@
 import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
- * @hide
+ * Base class for media route provider services.
+ * <p>
+ * The system media router service will bind to media route provider services when a
+ * {@link RouteDiscoveryPreference discovery preference} is registered via
+ * a {@link MediaRouter2 media router} by an application.
+ * </p><p>
+ * To implement your own media route provider service, extend this class and
+ * override {@link #onDiscoveryPreferenceChanged(RouteDiscoveryPreference)} to publish
+ * {@link MediaRoute2Info routes}.
+ * </p>
  */
 public abstract class MediaRoute2ProviderService extends Service {
     private static final String TAG = "MR2ProviderService";
@@ -55,13 +66,14 @@
     private MediaRoute2ProviderInfo mProviderInfo;
 
     @GuardedBy("mSessionLock")
-    private ArrayMap<Integer, RouteSessionInfo> mSessionInfo = new ArrayMap<>();
+    private ArrayMap<String, RoutingSessionInfo> mSessionInfo = new ArrayMap<>();
 
     public MediaRoute2ProviderService() {
         mHandler = new Handler(Looper.getMainLooper());
     }
 
     @Override
+    @NonNull
     public IBinder onBind(@NonNull Intent intent) {
         //TODO: Allow binding from media router service only?
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
@@ -78,6 +90,7 @@
      *
      * @param routeId the id of the target route
      * @param request the media control request intent
+     * @hide
      */
     //TODO: Discuss what to use for request (e.g., Intent? Request class?)
     public abstract void onControlRequest(@NonNull String routeId, @NonNull Intent request);
@@ -87,6 +100,7 @@
      *
      * @param routeId the id of the route
      * @param volume the target volume
+     * @hide
      */
     public abstract void onSetVolume(@NonNull String routeId, int volume);
 
@@ -95,6 +109,7 @@
      *
      * @param routeId id of the route
      * @param delta the delta to add to the current volume
+     * @hide
      */
     public abstract void onUpdateVolume(@NonNull String routeId, int delta);
 
@@ -104,19 +119,24 @@
      * @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.
+     * @hide
      */
     @Nullable
-    public final RouteSessionInfo getSessionInfo(int sessionId) {
+    public final RoutingSessionInfo getSessionInfo(@NonNull String sessionId) {
+        if (TextUtils.isEmpty(sessionId)) {
+            throw new IllegalArgumentException("sessionId must not be empty");
+        }
         synchronized (mSessionLock) {
             return mSessionInfo.get(sessionId);
         }
     }
 
     /**
-     * Gets the list of {@link RouteSessionInfo session info} that the provider service maintains.
+     * Gets the list of {@link RoutingSessionInfo session info} that the provider service maintains.
+     * @hide
      */
     @NonNull
-    public final List<RouteSessionInfo> getAllSessionInfo() {
+    public final List<RoutingSessionInfo> getAllSessionInfo() {
         synchronized (mSessionLock) {
             return new ArrayList<>(mSessionInfo.values());
         }
@@ -125,20 +145,16 @@
     /**
      * Updates the information of a session.
      * If the session is destroyed or not created before, it will be ignored.
-     * A session will be destroyed if it has no selected route.
      * Call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify clients of
      * session info changes.
      *
      * @param sessionInfo new session information
-     * @see #notifySessionCreated(RouteSessionInfo, long)
+     * @see #notifySessionCreated(RoutingSessionInfo, long)
+     * @hide
      */
-    public final void updateSessionInfo(@NonNull RouteSessionInfo sessionInfo) {
+    public final void updateSessionInfo(@NonNull RoutingSessionInfo sessionInfo) {
         Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
-        int sessionId = sessionInfo.getSessionId();
-        if (sessionInfo.getSelectedRoutes().isEmpty()) {
-            releaseSession(sessionId);
-            return;
-        }
+        String sessionId = sessionInfo.getId();
 
         synchronized (mSessionLock) {
             if (mSessionInfo.containsKey(sessionId)) {
@@ -157,10 +173,10 @@
      * TODO: This method is temporary, only created for tests. Remove when the alternative is ready.
      * @hide
      */
-    public final void notifySessionInfoChanged(@NonNull RouteSessionInfo sessionInfo) {
+    public final void notifySessionInfoChanged(@NonNull RoutingSessionInfo sessionInfo) {
         Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
 
-        int sessionId = sessionInfo.getSessionId();
+        String sessionId = sessionInfo.getId();
         synchronized (mSessionLock) {
             if (mSessionInfo.containsKey(sessionId)) {
                 mSessionInfo.put(sessionId, sessionInfo);
@@ -185,22 +201,24 @@
      * controlled, pass a {@link Bundle} that contains how to control it.
      *
      * @param sessionInfo information of the new session.
-     *                    The {@link RouteSessionInfo#getSessionId() id} of the session must be
+     *                    The {@link RoutingSessionInfo#getId() id} of the session must be
      *                    unique. Pass {@code null} to reject the request or inform clients that
      *                    session creation is failed.
      * @param requestId id of the previous request to create this session
+     * @hide
      */
     // TODO: fail reason?
     // TODO: Maybe better to create notifySessionCreationFailed?
-    public final void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) {
+    public final void notifySessionCreated(@Nullable RoutingSessionInfo sessionInfo,
+            long requestId) {
         if (sessionInfo != null) {
-            int sessionId = sessionInfo.getSessionId();
+            String sessionId = sessionInfo.getId();
             synchronized (mSessionLock) {
                 if (mSessionInfo.containsKey(sessionId)) {
                     Log.w(TAG, "Ignoring duplicate session id.");
                     return;
                 }
-                mSessionInfo.put(sessionInfo.getSessionId(), sessionInfo);
+                mSessionInfo.put(sessionInfo.getId(), sessionInfo);
             }
             schedulePublishState();
         }
@@ -220,11 +238,15 @@
      * {@link #onDestroySession} is called if the session is released.
      *
      * @param sessionId id of the session to be released
-     * @see #onDestroySession(int, RouteSessionInfo)
+     * @see #onDestroySession(String, RoutingSessionInfo)
+     * @hide
      */
-    public final void releaseSession(int sessionId) {
+    public final void releaseSession(@NonNull String sessionId) {
+        if (TextUtils.isEmpty(sessionId)) {
+            throw new IllegalArgumentException("sessionId must not be empty");
+        }
         //TODO: notify media router service of release.
-        RouteSessionInfo sessionInfo;
+        RoutingSessionInfo sessionInfo;
         synchronized (mSessionLock) {
             sessionInfo = mSessionInfo.remove(sessionId);
         }
@@ -238,19 +260,20 @@
     /**
      * 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, long)}
+     * session info. Call {@link #notifySessionCreated(RoutingSessionInfo, long)}
      * with the given {@code requestId} to notify the information of a new session.
      * If you can't create the session or want to reject the request, pass {@code null}
-     * as session info in {@link #notifySessionCreated(RouteSessionInfo, long)}
+     * as session info in {@link #notifySessionCreated(RoutingSessionInfo, long)}
      * with the given {@code requestId}.
      *
      * @param packageName the package name of the application that selected the route
      * @param routeId the id of the route initially being connected
-     * @param controlCategory the control category of the new session
+     * @param routeFeature the route feature of the new session
      * @param requestId the id of this session creation request
+     * @hide
      */
     public abstract void onCreateSession(@NonNull String packageName, @NonNull String routeId,
-            @NonNull String controlCategory, long requestId);
+            @NonNull String routeFeature, long requestId);
 
     /**
      * Called when a session is about to be destroyed.
@@ -259,52 +282,79 @@
      *
      * @param sessionId id of the session being destroyed.
      * @param lastSessionInfo information of the session being destroyed.
-     * @see #releaseSession(int)
+     * @see #releaseSession(String)
+     * @hide
      */
-    public abstract void onDestroySession(int sessionId, @NonNull RouteSessionInfo lastSessionInfo);
+    public abstract void onDestroySession(@NonNull String sessionId,
+            @NonNull RoutingSessionInfo lastSessionInfo);
 
     //TODO: make a way to reject the request
     /**
      * Called when a client requests selecting a route for the session.
-     * After the route is selected, call {@link #updateSessionInfo(RouteSessionInfo)} to update
+     * After the route is selected, call {@link #updateSessionInfo(RoutingSessionInfo)} 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)
+     * @see #updateSessionInfo(RoutingSessionInfo)
+     * @hide
      */
-    public abstract void onSelectRoute(int sessionId, @NonNull String routeId);
+    public abstract void onSelectRoute(@NonNull String sessionId, @NonNull String routeId);
 
     //TODO: make a way to reject the request
     /**
      * Called when a client requests deselecting a route from the session.
-     * After the route is deselected, call {@link #updateSessionInfo(RouteSessionInfo)} to update
+     * After the route is deselected, call {@link #updateSessionInfo(RoutingSessionInfo)} 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
+     * @hide
      */
-    public abstract void onDeselectRoute(int sessionId, @NonNull String routeId);
+    public abstract void onDeselectRoute(@NonNull String sessionId, @NonNull String routeId);
 
     //TODO: make a way to reject the request
     /**
      * Called when a client requests transferring a session to a route.
-     * After the transfer is finished, call {@link #updateSessionInfo(RouteSessionInfo)} to update
+     * After the transfer is finished, call {@link #updateSessionInfo(RoutingSessionInfo)} 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
+     * @hide
      */
-    public abstract void onTransferToRoute(int sessionId, @NonNull String routeId);
+    public abstract void onTransferToRoute(@NonNull String sessionId, @NonNull String routeId);
 
     /**
-     * Updates provider info and publishes routes and session info.
+     * Called when the {@link RouteDiscoveryPreference discovery preference} has changed.
+     * <p>
+     * Whenever an application registers a {@link MediaRouter2.RouteCallback callback},
+     * it also provides a discovery preference to specify features of routes that it is interested
+     * in. The media router combines all of these discovery request into a single discovery
+     * preference and notifies each provider.
+     * </p><p>
+     * The provider should examine {@link RouteDiscoveryPreference#getPreferredFeatures()
+     * preferred features} in the discovery preference to determine what kind of routes it should
+     * try to discover and whether it should perform active or passive scans. In many cases,
+     * the provider may be able to save power by not performing any scans when the request doesn't
+     * have any matching route features.
+     * </p>
+     *
+     * @param preference the new discovery preference
      */
-    public final void updateProviderInfo(@NonNull MediaRoute2ProviderInfo providerInfo) {
-        mProviderInfo = Objects.requireNonNull(providerInfo, "providerInfo must not be null");
+    public void onDiscoveryPreferenceChanged(@NonNull RouteDiscoveryPreference preference) {}
+
+    /**
+     * Updates routes of the provider and notifies the system media router service.
+     */
+    public final void notifyRoutes(@NonNull Collection<MediaRoute2Info> routes) {
+        Objects.requireNonNull(routes, "routes must not be null");
+        mProviderInfo = new MediaRoute2ProviderInfo.Builder()
+                .addRoutes(routes)
+                .build();
         schedulePublishState();
     }
 
@@ -328,7 +378,7 @@
             return;
         }
 
-        List<RouteSessionInfo> sessionInfos;
+        List<RoutingSessionInfo> sessionInfos;
         synchronized (mSessionLock) {
             sessionInfos = new ArrayList<>(mSessionInfo.values());
         }
@@ -357,46 +407,62 @@
 
         @Override
         public void requestCreateSession(String packageName, String routeId,
-                String controlCategory, long requestId) {
+                String routeFeature, long requestId) {
             if (!checkCallerisSystem()) {
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession,
-                    MediaRoute2ProviderService.this, packageName, routeId, controlCategory,
+                    MediaRoute2ProviderService.this, packageName, routeId, routeFeature,
                     requestId));
         }
         @Override
-        public void releaseSession(int sessionId) {
+        public void releaseSession(@NonNull String sessionId) {
             if (!checkCallerisSystem()) {
                 return;
             }
+            if (TextUtils.isEmpty(sessionId)) {
+                Log.w(TAG, "releaseSession: Ignoring empty sessionId from system service.");
+                return;
+            }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::releaseSession,
                     MediaRoute2ProviderService.this, sessionId));
         }
 
         @Override
-        public void selectRoute(int sessionId, String routeId) {
+        public void selectRoute(@NonNull String sessionId, String routeId) {
             if (!checkCallerisSystem()) {
                 return;
             }
+            if (TextUtils.isEmpty(sessionId)) {
+                Log.w(TAG, "selectRoute: Ignoring empty sessionId from system service.");
+                return;
+            }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute,
                     MediaRoute2ProviderService.this, sessionId, routeId));
         }
 
         @Override
-        public void deselectRoute(int sessionId, String routeId) {
+        public void deselectRoute(@NonNull String sessionId, String routeId) {
             if (!checkCallerisSystem()) {
                 return;
             }
+            if (TextUtils.isEmpty(sessionId)) {
+                Log.w(TAG, "deselectRoute: Ignoring empty sessionId from system service.");
+                return;
+            }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onDeselectRoute,
                     MediaRoute2ProviderService.this, sessionId, routeId));
         }
 
         @Override
-        public void transferToRoute(int sessionId, String routeId) {
+        public void transferToRoute(@NonNull String sessionId, String routeId) {
             if (!checkCallerisSystem()) {
                 return;
             }
+            if (TextUtils.isEmpty(sessionId)) {
+                Log.w(TAG, "transferToRoute: Ignoring empty sessionId from system service.");
+                return;
+            }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute,
                     MediaRoute2ProviderService.this, sessionId, routeId));
         }
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index f5cfde4b..6d37c2d 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -18,10 +18,7 @@
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
 import android.annotation.CallbackExecutor;
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -37,9 +34,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 
-import java.lang.annotation.Retention;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -48,52 +43,16 @@
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
 
 /**
- * A new Media Router
- * @hide
+ * Media Router 2 allows applications to control the routing of media channels
+ * and streams from the current device to remote speakers and devices.
  *
  * TODO: Add method names at the beginning of log messages. (e.g. changeSessionInfoOnHandler)
  *       Not only MediaRouter2, but also to service / manager / provider.
  */
 public class MediaRouter2 {
-
-    /** @hide */
-    @Retention(SOURCE)
-    @IntDef(value = {
-            SELECT_REASON_UNKNOWN,
-            SELECT_REASON_USER_SELECTED,
-            SELECT_REASON_FALLBACK,
-            SELECT_REASON_SYSTEM_SELECTED})
-    public @interface SelectReason {}
-
-    /**
-     * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the reason
-     * the route was selected is unknown.
-     */
-    public static final int SELECT_REASON_UNKNOWN = 0;
-
-    /**
-     * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the route
-     * is selected in response to a user's request. For example, when a user has selected
-     * a different device to play media to.
-     */
-    public static final int SELECT_REASON_USER_SELECTED = 1;
-
-    /**
-     * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the route
-     * is selected as a fallback route. For example, when Wi-Fi is disconnected, the device speaker
-     * may be selected as a fallback route.
-     */
-    public static final int SELECT_REASON_FALLBACK = 2;
-
-    /**
-     * This is passed from {@link com.android.server.media.MediaRouterService} when the route
-     * is selected in response to a request from other apps (e.g. System UI).
-     * @hide
-     */
-    public static final int SELECT_REASON_SYSTEM_SELECTED = 3;
-
     private static final String TAG = "MR2";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final Object sRouterLock = new Object();
@@ -118,14 +77,14 @@
     final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
 
     @GuardedBy("sLock")
-    private List<String> mControlCategories = Collections.emptyList();
+    private RouteDiscoveryPreference mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
 
     // TODO: Make MediaRouter2 is always connected to the MediaRouterService.
     @GuardedBy("sLock")
     Client2 mClient;
 
     @GuardedBy("sLock")
-    private Map<String, RouteSessionController> mSessionControllers = new ArrayMap<>();
+    private Map<String, RoutingController> mRoutingControllers = new ArrayMap<>();
 
     private AtomicInteger mSessionCreationRequestCnt = new AtomicInteger(1);
 
@@ -137,6 +96,7 @@
     /**
      * Gets an instance of the media router associated with the context.
      */
+    @NonNull
     public static MediaRouter2 getInstance(@NonNull Context context) {
         Objects.requireNonNull(context, "context must not be null");
         synchronized (sRouterLock) {
@@ -152,7 +112,6 @@
         mMediaRouterService = IMediaRouterService.Stub.asInterface(
                 ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
         mPackageName = mContext.getPackageName();
-        //TODO: read control categories from the manifest
         mHandler = new Handler(Looper.getMainLooper());
 
         List<MediaRoute2Info> currentSystemRoutes = null;
@@ -177,9 +136,9 @@
      * @hide
      */
     public static boolean checkRouteListContainsRouteId(@NonNull List<MediaRoute2Info> routeList,
-            @NonNull String uniqueRouteId) {
+            @NonNull String routeId) {
         for (MediaRoute2Info info : routeList) {
-            if (TextUtils.equals(uniqueRouteId, info.getUniqueId())) {
+            if (TextUtils.equals(routeId, info.getId())) {
                 return true;
             }
         }
@@ -188,24 +147,18 @@
 
     /**
      * Registers a callback to discover routes and to receive events when they change.
-     */
-    public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull RouteCallback routeCallback) {
-        registerRouteCallback(executor, routeCallback, 0);
-    }
-
-    /**
-     * Registers a callback to discover routes and to receive events when they change.
      * <p>
      * If you register the same callback twice or more, it will be ignored.
      * </p>
      */
     public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor,
-            @NonNull RouteCallback routeCallback, int flags) {
+            @NonNull RouteCallback routeCallback,
+            @NonNull RouteDiscoveryPreference preference) {
         Objects.requireNonNull(executor, "executor must not be null");
         Objects.requireNonNull(routeCallback, "callback must not be null");
+        Objects.requireNonNull(preference, "preference must not be null");
 
-        RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, flags);
+        RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, preference);
         if (!mRouteCallbackRecords.addIfAbsent(record)) {
             Log.w(TAG, "Ignoring the same callback");
             return;
@@ -216,15 +169,14 @@
                 Client2 client = new Client2();
                 try {
                     mMediaRouterService.registerClient2(client, mPackageName);
-                    mMediaRouterService.setControlCategories(client, mControlCategories);
+                    updateDiscoveryRequestLocked();
+                    mMediaRouterService.setDiscoveryRequest2(client, mDiscoveryPreference);
                     mClient = client;
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to register media router.", ex);
                 }
             }
         }
-
-        //TODO: Update discovery request here.
     }
 
     /**
@@ -238,7 +190,7 @@
         Objects.requireNonNull(routeCallback, "callback must not be null");
 
         if (!mRouteCallbackRecords.remove(
-                new RouteCallbackRecord(null, routeCallback, 0))) {
+                new RouteCallbackRecord(null, routeCallback, null))) {
             Log.w(TAG, "Ignoring unknown callback");
             return;
         }
@@ -256,30 +208,10 @@
         }
     }
 
-    //TODO(b/139033746): Rename "Control Category" when it's finalized.
-    /**
-     * Sets the control categories of the application.
-     * Routes that support at least one of the given control categories are handled
-     * by the media router.
-     */
-    public void setControlCategories(@NonNull Collection<String> controlCategories) {
-        Objects.requireNonNull(controlCategories, "control categories must not be null");
-
-        List<String> newControlCategories = new ArrayList<>(controlCategories);
-
-        synchronized (sRouterLock) {
-            mShouldUpdateRoutes = true;
-
-            // invoke callbacks due to control categories change
-            handleControlCategoriesChangedLocked(newControlCategories);
-            if (mClient != null) {
-                try {
-                    mMediaRouterService.setControlCategories(mClient, mControlCategories);
-                } catch (RemoteException ex) {
-                    Log.e(TAG, "Unable to set control categories.", ex);
-                }
-            }
-        }
+    private void updateDiscoveryRequestLocked() {
+        mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
+                mRouteCallbackRecords.stream().map(record -> record.mPreference).collect(
+                        Collectors.toList())).build();
     }
 
     /**
@@ -287,8 +219,8 @@
      * known to the media router.
      * Please note that the list can be changed before callbacks are invoked.
      *
-     * @return the list of routes that support at least one of the control categories set by
-     * the application
+     * @return the list of routes that contains at least one of the route features in discovery
+     * preferences registered by the application
      */
     @NonNull
     public List<MediaRoute2Info> getRoutes() {
@@ -298,7 +230,7 @@
 
                 List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
                 for (MediaRoute2Info route : mRoutes.values()) {
-                    if (route.supportsControlCategories(mControlCategories)) {
+                    if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
                         filteredRoutes.add(route);
                     }
                 }
@@ -309,12 +241,13 @@
     }
 
     /**
-     * Registers a callback to get updates on creations and changes of route sessions.
+     * Registers a callback to get updates on creations and changes of routing sessions.
      * If you register the same callback twice or more, it will be ignored.
      *
      * @param executor the executor to execute the callback on
      * @param callback the callback to register
      * @see #unregisterSessionCallback
+     * @hide
      */
     @NonNull
     public void registerSessionCallback(@CallbackExecutor Executor executor,
@@ -335,6 +268,7 @@
      *
      * @param callback the callback to unregister
      * @see #registerSessionCallback
+     * @hide
      */
     @NonNull
     public void unregisterSessionCallback(@NonNull SessionCallback callback) {
@@ -350,26 +284,26 @@
      * Requests the media route provider service to create a session with the given 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 routeFeature the route feature of the session. Should not be empty.
      *
      * @see SessionCallback#onSessionCreated
      * @see SessionCallback#onSessionCreationFailed
+     * @hide
      */
     @NonNull
     public void requestCreateSession(@NonNull MediaRoute2Info route,
-            @NonNull String controlCategory) {
+            @NonNull String routeFeature) {
         Objects.requireNonNull(route, "route must not be null");
-        if (TextUtils.isEmpty(controlCategory)) {
-            throw new IllegalArgumentException("controlCategory must not be empty");
+        if (TextUtils.isEmpty(routeFeature)) {
+            throw new IllegalArgumentException("routeFeature must not be empty");
         }
         // TODO: Check the given route exists
-        // TODO: Check the route supports the given controlCategory
+        // TODO: Check the route supports the given routeFeature
 
         final int requestId;
         requestId = mSessionCreationRequestCnt.getAndIncrement();
 
-        SessionCreationRequest request = new SessionCreationRequest(
-                requestId, route, controlCategory);
+        SessionCreationRequest request = new SessionCreationRequest(requestId, route, routeFeature);
         mSessionCreationRequests.add(request);
 
         Client2 client;
@@ -378,8 +312,7 @@
         }
         if (client != null) {
             try {
-                mMediaRouterService.requestCreateSession(
-                        client, route, controlCategory, requestId);
+                mMediaRouterService.requestCreateSession(client, route, routeFeature, requestId);
             } catch (RemoteException ex) {
                 Log.e(TAG, "Unable to request to create session.", ex);
                 mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
@@ -393,6 +326,7 @@
      *
      * @param route the route that will receive the control request
      * @param request the media control request
+     * @hide
      */
     //TODO: Discuss what to use for request (e.g., Intent? Request class?)
     //TODO: Provide a way to obtain the result
@@ -420,6 +354,7 @@
      * </p>
      *
      * @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}.
+     * @hide
      */
     public void requestSetVolume(@NonNull MediaRoute2Info route, int volume) {
         Objects.requireNonNull(route, "route must not be null");
@@ -444,6 +379,7 @@
      * </p>
      *
      * @param delta The delta to add to the current volume.
+     * @hide
      */
     public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) {
         Objects.requireNonNull(route, "route must not be null");
@@ -461,36 +397,6 @@
         }
     }
 
-    private void handleControlCategoriesChangedLocked(List<String> newControlCategories) {
-        List<MediaRoute2Info> addedRoutes = new ArrayList<>();
-        List<MediaRoute2Info> removedRoutes = new ArrayList<>();
-
-        List<String> prevControlCategories = mControlCategories;
-        mControlCategories = newControlCategories;
-
-        for (MediaRoute2Info route : mRoutes.values()) {
-            boolean preSupported = route.supportsControlCategories(prevControlCategories);
-            boolean postSupported = route.supportsControlCategories(newControlCategories);
-            if (preSupported == postSupported) {
-                continue;
-            }
-            if (preSupported) {
-                removedRoutes.add(route);
-            } else {
-                addedRoutes.add(route);
-            }
-        }
-
-        if (removedRoutes.size() > 0) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2::notifyRoutesRemoved,
-                    MediaRouter2.this, removedRoutes));
-        }
-        if (addedRoutes.size() > 0) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2::notifyRoutesAdded,
-                    MediaRouter2.this, addedRoutes));
-        }
-    }
-
     void addRoutesOnHandler(List<MediaRoute2Info> routes) {
         // TODO: When onRoutesAdded is first called,
         //  1) clear mRoutes before adding the routes
@@ -499,8 +405,8 @@
         List<MediaRoute2Info> addedRoutes = new ArrayList<>();
         synchronized (sRouterLock) {
             for (MediaRoute2Info route : routes) {
-                mRoutes.put(route.getUniqueId(), route);
-                if (route.supportsControlCategories(mControlCategories)) {
+                mRoutes.put(route.getId(), route);
+                if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
                     addedRoutes.add(route);
                 }
             }
@@ -515,8 +421,8 @@
         List<MediaRoute2Info> removedRoutes = new ArrayList<>();
         synchronized (sRouterLock) {
             for (MediaRoute2Info route : routes) {
-                mRoutes.remove(route.getUniqueId());
-                if (route.supportsControlCategories(mControlCategories)) {
+                mRoutes.remove(route.getId());
+                if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
                     removedRoutes.add(route);
                 }
             }
@@ -531,8 +437,8 @@
         List<MediaRoute2Info> changedRoutes = new ArrayList<>();
         synchronized (sRouterLock) {
             for (MediaRoute2Info route : routes) {
-                mRoutes.put(route.getUniqueId(), route);
-                if (route.supportsControlCategories(mControlCategories)) {
+                mRoutes.put(route.getId(), route);
+                if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
                     changedRoutes.add(route);
                 }
             }
@@ -549,7 +455,7 @@
      * <p>
      * Pass {@code null} to sessionInfo for the failure case.
      */
-    void createControllerOnHandler(@Nullable RouteSessionInfo sessionInfo, int requestId) {
+    void createControllerOnHandler(@Nullable RoutingSessionInfo sessionInfo, int requestId) {
         SessionCreationRequest matchingRequest = null;
         for (SessionCreationRequest request : mSessionCreationRequests) {
             if (request.mRequestId == requestId) {
@@ -562,27 +468,27 @@
             mSessionCreationRequests.remove(matchingRequest);
 
             MediaRoute2Info requestedRoute = matchingRequest.mRoute;
-            String requestedControlCategory = matchingRequest.mControlCategory;
+            String requestedRouteFeature = matchingRequest.mRouteFeature;
 
             if (sessionInfo == null) {
                 // TODO: We may need to distinguish between failure and rejection.
                 //       One way can be introducing 'reason'.
-                notifySessionCreationFailed(requestedRoute, requestedControlCategory);
+                notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
                 return;
-            } else if (!TextUtils.equals(requestedControlCategory,
-                    sessionInfo.getControlCategory())) {
-                Log.w(TAG, "The session has different control category from what we requested. "
-                        + "(requested=" + requestedControlCategory
-                        + ", actual=" + sessionInfo.getControlCategory()
+            } else if (!TextUtils.equals(requestedRouteFeature,
+                    sessionInfo.getRouteFeature())) {
+                Log.w(TAG, "The session has different route feature from what we requested. "
+                        + "(requested=" + requestedRouteFeature
+                        + ", actual=" + sessionInfo.getRouteFeature()
                         + ")");
-                notifySessionCreationFailed(requestedRoute, requestedControlCategory);
+                notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
                 return;
             } else if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) {
                 Log.w(TAG, "The session does not contain the requested route. "
                         + "(requestedRouteId=" + requestedRoute.getId()
                         + ", actualRoutes=" + sessionInfo.getSelectedRoutes()
                         + ")");
-                notifySessionCreationFailed(requestedRoute, requestedControlCategory);
+                notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
                 return;
             } else if (!TextUtils.equals(requestedRoute.getProviderId(),
                     sessionInfo.getProviderId())) {
@@ -590,69 +496,69 @@
                         + "(requested route's providerId=" + requestedRoute.getProviderId()
                         + ", actual providerId=" + sessionInfo.getProviderId()
                         + ")");
-                notifySessionCreationFailed(requestedRoute, requestedControlCategory);
+                notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
                 return;
             }
         }
 
         if (sessionInfo != null) {
-            RouteSessionController controller = new RouteSessionController(sessionInfo);
+            RoutingController controller = new RoutingController(sessionInfo);
             synchronized (sRouterLock) {
-                mSessionControllers.put(controller.getUniqueSessionId(), controller);
+                mRoutingControllers.put(controller.getSessionId(), controller);
             }
             notifySessionCreated(controller);
         }
     }
 
-    void changeSessionInfoOnHandler(RouteSessionInfo sessionInfo) {
+    void changeSessionInfoOnHandler(RoutingSessionInfo sessionInfo) {
         if (sessionInfo == null) {
             Log.w(TAG, "changeSessionInfoOnHandler: Ignoring null sessionInfo.");
             return;
         }
 
-        RouteSessionController matchingController;
+        RoutingController matchingController;
         synchronized (sRouterLock) {
-            matchingController = mSessionControllers.get(sessionInfo.getUniqueSessionId());
+            matchingController = mRoutingControllers.get(sessionInfo.getId());
         }
 
         if (matchingController == null) {
             Log.w(TAG, "changeSessionInfoOnHandler: Matching controller not found. uniqueSessionId="
-                    + sessionInfo.getUniqueSessionId());
+                    + sessionInfo.getId());
             return;
         }
 
-        RouteSessionInfo oldInfo = matchingController.getRouteSessionInfo();
+        RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
         if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
             Log.w(TAG, "changeSessionInfoOnHandler: Provider IDs are not matched. old="
                     + oldInfo.getProviderId() + ", new=" + sessionInfo.getProviderId());
             return;
         }
 
-        matchingController.setRouteSessionInfo(sessionInfo);
+        matchingController.setRoutingSessionInfo(sessionInfo);
         notifySessionInfoChanged(matchingController, oldInfo, sessionInfo);
     }
 
-    void releaseControllerOnHandler(RouteSessionInfo sessionInfo) {
+    void releaseControllerOnHandler(RoutingSessionInfo sessionInfo) {
         if (sessionInfo == null) {
             Log.w(TAG, "releaseControllerOnHandler: Ignoring null sessionInfo.");
             return;
         }
 
-        final String uniqueSessionId = sessionInfo.getUniqueSessionId();
-        RouteSessionController matchingController;
+        final String uniqueSessionId = sessionInfo.getId();
+        RoutingController matchingController;
         synchronized (sRouterLock) {
-            matchingController = mSessionControllers.get(uniqueSessionId);
+            matchingController = mRoutingControllers.get(uniqueSessionId);
         }
 
         if (matchingController == null) {
             if (DEBUG) {
                 Log.d(TAG, "releaseControllerOnHandler: Matching controller not found. "
-                        + "uniqueSessionId=" + sessionInfo.getUniqueSessionId());
+                        + "uniqueSessionId=" + sessionInfo.getId());
             }
             return;
         }
 
-        RouteSessionInfo oldInfo = matchingController.getRouteSessionInfo();
+        RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
         if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
             Log.w(TAG, "releaseControllerOnHandler: Provider IDs are not matched. old="
                     + oldInfo.getProviderId() + ", new=" + sessionInfo.getProviderId());
@@ -660,49 +566,66 @@
         }
 
         synchronized (sRouterLock) {
-            mSessionControllers.remove(uniqueSessionId, matchingController);
+            mRoutingControllers.remove(uniqueSessionId, matchingController);
         }
         matchingController.release();
         notifyControllerReleased(matchingController);
     }
 
+    private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
+            RouteDiscoveryPreference discoveryRequest) {
+        return routes.stream()
+                .filter(
+                        route -> route.hasAnyFeatures(discoveryRequest.getPreferredFeatures()))
+                .collect(Collectors.toList());
+    }
+
     private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
         for (RouteCallbackRecord record: mRouteCallbackRecords) {
-            record.mExecutor.execute(
-                    () -> record.mRouteCallback.onRoutesAdded(routes));
+            List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
+            if (!filteredRoutes.isEmpty()) {
+                record.mExecutor.execute(
+                        () -> record.mRouteCallback.onRoutesAdded(filteredRoutes));
+            }
         }
     }
 
     private void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
         for (RouteCallbackRecord record: mRouteCallbackRecords) {
-            record.mExecutor.execute(
-                    () -> record.mRouteCallback.onRoutesRemoved(routes));
+            List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
+            if (!filteredRoutes.isEmpty()) {
+                record.mExecutor.execute(
+                        () -> record.mRouteCallback.onRoutesRemoved(filteredRoutes));
+            }
         }
     }
 
     private void notifyRoutesChanged(List<MediaRoute2Info> routes) {
         for (RouteCallbackRecord record: mRouteCallbackRecords) {
-            record.mExecutor.execute(
-                    () -> record.mRouteCallback.onRoutesChanged(routes));
+            List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
+            if (!filteredRoutes.isEmpty()) {
+                record.mExecutor.execute(
+                        () -> record.mRouteCallback.onRoutesChanged(filteredRoutes));
+            }
         }
     }
 
-    private void notifySessionCreated(RouteSessionController controller) {
+    private void notifySessionCreated(RoutingController controller) {
         for (SessionCallbackRecord record: mSessionCallbackRecords) {
             record.mExecutor.execute(
                     () -> record.mSessionCallback.onSessionCreated(controller));
         }
     }
 
-    private void notifySessionCreationFailed(MediaRoute2Info route, String controlCategory) {
+    private void notifySessionCreationFailed(MediaRoute2Info route, String routeFeature) {
         for (SessionCallbackRecord record: mSessionCallbackRecords) {
             record.mExecutor.execute(
-                    () -> record.mSessionCallback.onSessionCreationFailed(route, controlCategory));
+                    () -> record.mSessionCallback.onSessionCreationFailed(route, routeFeature));
         }
     }
 
-    private void notifySessionInfoChanged(RouteSessionController controller,
-            RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+    private void notifySessionInfoChanged(RoutingController controller,
+            RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) {
         for (SessionCallbackRecord record: mSessionCallbackRecords) {
             record.mExecutor.execute(
                     () -> record.mSessionCallback.onSessionInfoChanged(
@@ -710,7 +633,7 @@
         }
     }
 
-    private void notifyControllerReleased(RouteSessionController controller) {
+    private void notifyControllerReleased(RoutingController controller) {
         for (SessionCallbackRecord record: mSessionCallbackRecords) {
             record.mExecutor.execute(
                     () -> record.mSessionCallback.onSessionReleased(controller));
@@ -751,23 +674,24 @@
 
     /**
      * Callback for receiving a result of session creation and session updates.
+     * @hide
      */
     public static class SessionCallback {
         /**
-         * Called when the route session is created by the route provider.
+         * Called when the routing session is created by the route provider.
          *
          * @param controller the controller to control the created session
          */
-        public void onSessionCreated(@NonNull RouteSessionController controller) {}
+        public void onSessionCreated(@NonNull RoutingController controller) {}
 
         /**
          * Called when the session creation request failed.
          *
          * @param requestedRoute the route info which was used for the request
-         * @param requestedControlCategory the control category which was used for the request
+         * @param requestedRouteFeature the route feature which was used for the request
          */
         public void onSessionCreationFailed(@NonNull MediaRoute2Info requestedRoute,
-                @NonNull String requestedControlCategory) {}
+                @NonNull String requestedRouteFeature) {}
 
         /**
          * Called when the session info has changed.
@@ -778,79 +702,69 @@
          * TODO: (Discussion) Do we really need newInfo? The controller has the newInfo.
          *       However. there can be timing issue if there is no newInfo.
          */
-        public void onSessionInfoChanged(@NonNull RouteSessionController controller,
-                @NonNull RouteSessionInfo oldInfo,
-                @NonNull RouteSessionInfo newInfo) {}
+        public void onSessionInfoChanged(@NonNull RoutingController controller,
+                @NonNull RoutingSessionInfo oldInfo,
+                @NonNull RoutingSessionInfo newInfo) {}
 
         /**
          * Called when the session is released by {@link MediaRoute2ProviderService}.
          * Before this method is called, the controller would be released by the system,
-         * which means the {@link RouteSessionController#isReleased()} will always return true
+         * which means the {@link RoutingController#isReleased()} will always return true
          * for the {@code controller} here.
          * <p>
-         * Note: Calling {@link RouteSessionController#release()} will <em>NOT</em> trigger
+         * Note: Calling {@link RoutingController#release()} will <em>NOT</em> trigger
          * this method to be called.
          *
          * TODO: Add tests for checking whether this method is called.
          * TODO: When service process dies, this should be called.
          *
-         * @see RouteSessionController#isReleased()
+         * @see RoutingController#isReleased()
          */
-        public void onSessionReleased(@NonNull RouteSessionController controller) {}
+        public void onSessionReleased(@NonNull RoutingController controller) {}
     }
 
     /**
-     * A class to control media route session in media route provider.
+     * A class to control media routing session in media route provider.
      * For example, selecting/deselcting/transferring routes to session can be done through this
      * class. Instances are created by {@link MediaRouter2}.
      *
      * TODO: Need to add toString()
+     * @hide
      */
-    public final class RouteSessionController {
+    public final class RoutingController {
         private final Object mControllerLock = new Object();
 
         @GuardedBy("mLock")
-        private RouteSessionInfo mSessionInfo;
+        private RoutingSessionInfo mSessionInfo;
 
         @GuardedBy("mLock")
         private volatile boolean mIsReleased;
 
-        RouteSessionController(@NonNull RouteSessionInfo sessionInfo) {
+        RoutingController(@NonNull RoutingSessionInfo sessionInfo) {
             mSessionInfo = sessionInfo;
         }
 
         /**
          * @return the ID of the session
          */
-        public int getSessionId() {
+        public String getSessionId() {
             synchronized (mControllerLock) {
-                return mSessionInfo.getSessionId();
+                return mSessionInfo.getId();
             }
         }
 
         /**
-         * @return the unique ID of the session
-         * @hide
+         * @return the feature which is used by the session mainly.
          */
         @NonNull
-        public String getUniqueSessionId() {
+        public String getRouteFeature() {
             synchronized (mControllerLock) {
-                return mSessionInfo.getUniqueSessionId();
+                return mSessionInfo.getRouteFeature();
             }
         }
 
         /**
-         * @return the category of routes that the session includes.
-         */
-        @NonNull
-        public String getControlCategory() {
-            synchronized (mControllerLock) {
-                return mSessionInfo.getControlCategory();
-            }
-        }
-
-        /**
-         * @return the control hints used to control route session if available.
+         * @return the control hints used to control routing session if available.
          */
         @Nullable
         public Bundle getControlHints() {
@@ -935,13 +849,13 @@
             }
 
             List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
-            if (checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) {
+            if (checkRouteListContainsRouteId(selectedRoutes, route.getId())) {
                 Log.w(TAG, "Ignoring selecting a route that is already selected. route=" + route);
                 return;
             }
 
             List<MediaRoute2Info> selectableRoutes = getSelectableRoutes();
-            if (!checkRouteListContainsRouteId(selectableRoutes, route.getUniqueId())) {
+            if (!checkRouteListContainsRouteId(selectableRoutes, route.getId())) {
                 Log.w(TAG, "Ignoring selecting a non-selectable route=" + route);
                 return;
             }
@@ -952,7 +866,7 @@
             }
             if (client != null) {
                 try {
-                    mMediaRouterService.selectRoute(client, getUniqueSessionId(), route);
+                    mMediaRouterService.selectRoute(client, getSessionId(), route);
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to select route for session.", ex);
                 }
@@ -982,13 +896,13 @@
             }
 
             List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
-            if (!checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) {
+            if (!checkRouteListContainsRouteId(selectedRoutes, route.getId())) {
                 Log.w(TAG, "Ignoring deselecting a route that is not selected. route=" + route);
                 return;
             }
 
             List<MediaRoute2Info> deselectableRoutes = getDeselectableRoutes();
-            if (!checkRouteListContainsRouteId(deselectableRoutes, route.getUniqueId())) {
+            if (!checkRouteListContainsRouteId(deselectableRoutes, route.getId())) {
                 Log.w(TAG, "Ignoring deselecting a non-deselectable route=" + route);
                 return;
             }
@@ -999,7 +913,7 @@
             }
             if (client != null) {
                 try {
-                    mMediaRouterService.deselectRoute(client, getUniqueSessionId(), route);
+                    mMediaRouterService.deselectRoute(client, getSessionId(), route);
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to remove route from session.", ex);
                 }
@@ -1029,14 +943,14 @@
             }
 
             List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
-            if (checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) {
+            if (checkRouteListContainsRouteId(selectedRoutes, route.getId())) {
                 Log.w(TAG, "Ignoring transferring to a route that is already added. route="
                         + route);
                 return;
             }
 
             List<MediaRoute2Info> transferrableRoutes = getTransferrableRoutes();
-            if (!checkRouteListContainsRouteId(transferrableRoutes, route.getUniqueId())) {
+            if (!checkRouteListContainsRouteId(transferrableRoutes, route.getId())) {
                 Log.w(TAG, "Ignoring transferring to a non-transferrable route=" + route);
                 return;
             }
@@ -1047,7 +961,7 @@
             }
             if (client != null) {
                 try {
-                    mMediaRouterService.transferToRoute(client, getUniqueSessionId(), route);
+                    mMediaRouterService.transferToRoute(client, getSessionId(), route);
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to transfer to route for session.", ex);
                 }
@@ -1072,26 +986,30 @@
 
             Client2 client;
             synchronized (sRouterLock) {
-                mSessionControllers.remove(getUniqueSessionId(), this);
+                mRoutingControllers.remove(getSessionId(), this);
                 client = mClient;
             }
             if (client != null) {
                 try {
-                    mMediaRouterService.releaseSession(client, getUniqueSessionId());
+                    mMediaRouterService.releaseSession(client, getSessionId());
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to notify of controller release", ex);
                 }
             }
         }
 
+        /**
+         * TODO: Change this to package private. (Hidden for debugging purposes)
+         * @hide
+         */
         @NonNull
-        RouteSessionInfo getRouteSessionInfo() {
+        public RoutingSessionInfo getRoutingSessionInfo() {
             synchronized (mControllerLock) {
                 return mSessionInfo;
             }
         }
 
-        void setRouteSessionInfo(@NonNull RouteSessionInfo info) {
+        void setRoutingSessionInfo(@NonNull RoutingSessionInfo info) {
             synchronized (mControllerLock) {
                 mSessionInfo = info;
             }
@@ -1100,11 +1018,12 @@
         // TODO: This method uses two locks (mLock outside, sLock inside).
         //       Check if there is any possiblity of deadlock.
         private List<MediaRoute2Info> getRoutesWithIdsLocked(List<String> routeIds) {
+
             List<MediaRoute2Info> routes = new ArrayList<>();
             synchronized (sRouterLock) {
+                // TODO: Maybe able to change using Collection.stream()?
                 for (String routeId : routeIds) {
-                    MediaRoute2Info route = mRoutes.get(
-                            MediaRoute2Info.toUniqueId(mSessionInfo.mProviderId, routeId));
+                    MediaRoute2Info route = mRoutes.get(routeId);
                     if (route != null) {
                         routes.add(route);
                     }
@@ -1117,13 +1036,13 @@
     final class RouteCallbackRecord {
         public final Executor mExecutor;
         public final RouteCallback mRouteCallback;
-        public final int mFlags;
+        public final RouteDiscoveryPreference mPreference;
 
         RouteCallbackRecord(@Nullable Executor executor, @NonNull RouteCallback routeCallback,
-                int flags) {
+                @Nullable RouteDiscoveryPreference preference) {
             mRouteCallback = routeCallback;
             mExecutor = executor;
-            mFlags = flags;
+            mPreference = preference;
         }
 
         @Override
@@ -1172,13 +1091,13 @@
 
     final class SessionCreationRequest {
         public final MediaRoute2Info mRoute;
-        public final String mControlCategory;
+        public final String mRouteFeature;
         public final int mRequestId;
 
         SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route,
-                @NonNull String controlCategory) {
+                @NonNull String routeFeature) {
             mRoute = route;
-            mControlCategory = controlCategory;
+            mRouteFeature = routeFeature;
             mRequestId = requestId;
         }
     }
@@ -1206,19 +1125,19 @@
         }
 
         @Override
-        public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, int requestId) {
+        public void notifySessionCreated(@Nullable RoutingSessionInfo sessionInfo, int requestId) {
             mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
                     MediaRouter2.this, sessionInfo, requestId));
         }
 
         @Override
-        public void notifySessionInfoChanged(@Nullable RouteSessionInfo sessionInfo) {
+        public void notifySessionInfoChanged(@Nullable RoutingSessionInfo sessionInfo) {
             mHandler.sendMessage(obtainMessage(MediaRouter2::changeSessionInfoOnHandler,
                     MediaRouter2.this, sessionInfo));
         }
 
         @Override
-        public void notifySessionReleased(RouteSessionInfo sessionInfo) {
+        public void notifySessionReleased(RoutingSessionInfo sessionInfo) {
             mHandler.sendMessage(obtainMessage(MediaRouter2::releaseControllerOnHandler,
                     MediaRouter2.this, sessionInfo));
         }
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 2e68e42..7022933 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -65,7 +65,7 @@
     @GuardedBy("mRoutesLock")
     private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
     @NonNull
-    final ConcurrentMap<String, List<String>> mControlCategoryMap = new ConcurrentHashMap<>();
+    final ConcurrentMap<String, List<String>> mPreferredFeaturesMap = new ConcurrentHashMap<>();
 
     private AtomicInteger mNextRequestId = new AtomicInteger(1);
 
@@ -144,7 +144,7 @@
                 }
                 //TODO: clear mRoutes?
                 mClient = null;
-                mControlCategoryMap.clear();
+                mPreferredFeaturesMap.clear();
             }
         }
     }
@@ -160,14 +160,14 @@
     public List<MediaRoute2Info> getAvailableRoutes(@NonNull String packageName) {
         Objects.requireNonNull(packageName, "packageName must not be null");
 
-        List<String> controlCategories = mControlCategoryMap.get(packageName);
-        if (controlCategories == null) {
+        List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
+        if (preferredFeatures == null) {
             return Collections.emptyList();
         }
         List<MediaRoute2Info> routes = new ArrayList<>();
         synchronized (mRoutesLock) {
             for (MediaRoute2Info route : mRoutes.values()) {
-                if (route.supportsControlCategories(controlCategories)) {
+                if (route.hasAnyFeatures(preferredFeatures)) {
                     routes.add(route);
                 }
             }
@@ -176,7 +176,7 @@
     }
 
     @NonNull
-    public List<RouteSessionInfo> getActiveSessions() {
+    public List<RoutingSessionInfo> getActiveSessions() {
         Client client;
         synchronized (sLock) {
             client = mClient;
@@ -295,7 +295,7 @@
     void addRoutesOnHandler(List<MediaRoute2Info> routes) {
         synchronized (mRoutesLock) {
             for (MediaRoute2Info route : routes) {
-                mRoutes.put(route.getUniqueId(), route);
+                mRoutes.put(route.getId(), route);
             }
         }
         if (routes.size() > 0) {
@@ -306,7 +306,7 @@
     void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
         synchronized (mRoutesLock) {
             for (MediaRoute2Info route : routes) {
-                mRoutes.remove(route.getUniqueId());
+                mRoutes.remove(route.getId());
             }
         }
         if (routes.size() > 0) {
@@ -317,7 +317,7 @@
     void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
         synchronized (mRoutesLock) {
             for (MediaRoute2Info route : routes) {
-                mRoutes.put(route.getUniqueId(), route);
+                mRoutes.put(route.getId(), route);
             }
         }
         if (routes.size() > 0) {
@@ -352,15 +352,15 @@
         }
     }
 
-    void updateControlCategories(String packageName, List<String> categories) {
-        List<String> prevCategories = mControlCategoryMap.put(packageName, categories);
-        if ((prevCategories == null && categories.size() == 0)
-                || Objects.equals(categories, prevCategories)) {
+    void updatePreferredFeatures(String packageName, List<String> preferredFeatures) {
+        List<String> prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures);
+        if ((prevFeatures == null && preferredFeatures.size() == 0)
+                || Objects.equals(preferredFeatures, prevFeatures)) {
             return;
         }
         for (CallbackRecord record : mCallbackRecords) {
-            record.mExecutor.execute(
-                    () -> record.mCallback.onControlCategoriesChanged(packageName, categories));
+            record.mExecutor.execute(() -> record.mCallback
+                    .onControlCategoriesChanged(packageName, preferredFeatures));
         }
     }
 
@@ -398,13 +398,13 @@
 
 
         /**
-         * Called when the control categories of an app is changed.
+         * Called when the preferred route features of an app is changed.
          *
          * @param packageName the package name of the application
-         * @param controlCategories the list of control categories set by an application.
+         * @param preferredFeatures the list of preferred route features set by an application.
          */
         public void onControlCategoriesChanged(@NonNull String packageName,
-                @NonNull List<String> controlCategories) {}
+                @NonNull List<String> preferredFeatures) {}
     }
 
     final class CallbackRecord {
@@ -440,10 +440,9 @@
                     MediaRouter2Manager.this, packageName, route));
         }
 
-        @Override
-        public void notifyControlCategoriesChanged(String packageName, List<String> categories) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateControlCategories,
-                    MediaRouter2Manager.this, packageName, categories));
+        public void notifyPreferredFeaturesChanged(String packageName, List<String> features) {
+            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updatePreferredFeatures,
+                    MediaRouter2Manager.this, packageName, features));
         }
 
         @Override
diff --git a/media/java/android/media/MediaRouter2Utils.java b/media/java/android/media/MediaRouter2Utils.java
new file mode 100644
index 0000000..4904582
--- /dev/null
+++ b/media/java/android/media/MediaRouter2Utils.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class MediaRouter2Utils {
+
+    static final String TAG = "MR2Utils";
+    static final String SEPARATOR = ":";
+
+    /**
+     * @hide
+     */
+    @NonNull
+    public static String toUniqueId(@NonNull String providerId, @NonNull String id) {
+        if (TextUtils.isEmpty(providerId)) {
+            Log.w(TAG, "toUniqueId: providerId shouldn't be empty");
+            return null;
+        }
+        if (TextUtils.isEmpty(id)) {
+            Log.w(TAG, "toUniqueId: id shouldn't be null");
+            return null;
+        }
+
+        return providerId + SEPARATOR + id;
+    }
+
+    /**
+     * Gets provider ID from unique ID.
+     * If the corresponding provider ID could not be generated, it will return null.
+     *
+     * @hide
+     */
+    @Nullable
+    public static String getProviderId(@NonNull String uniqueId) {
+        if (TextUtils.isEmpty(uniqueId)) {
+            Log.w(TAG, "getProviderId: uniqueId shouldn't be empty");
+            return null;
+        }
+
+        int firstIndexOfSeparator = uniqueId.indexOf(SEPARATOR);
+        if (firstIndexOfSeparator == -1) {
+            return null;
+        }
+
+        String providerId = uniqueId.substring(0, firstIndexOfSeparator);
+        if (TextUtils.isEmpty(providerId)) {
+            return null;
+        }
+
+        return providerId;
+    }
+
+    /**
+     * Gets the original ID (i.e. non-unique route/session ID) from unique ID.
+     * If the corresponding ID could not be generated, it will return null.
+     *
+     * @hide
+     */
+    @Nullable
+    public static String getOriginalId(@NonNull String uniqueId) {
+        if (TextUtils.isEmpty(uniqueId)) {
+            Log.w(TAG, "getOriginalId: uniqueId shouldn't be empty");
+            return null;
+        }
+
+        int firstIndexOfSeparator = uniqueId.indexOf(SEPARATOR);
+        if (firstIndexOfSeparator == -1 || firstIndexOfSeparator + 1 >= uniqueId.length()) {
+            return null;
+        }
+
+        String providerId = uniqueId.substring(firstIndexOfSeparator + 1);
+        if (TextUtils.isEmpty(providerId)) {
+            return null;
+        }
+
+        return providerId;
+    }
+}
diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java
index 40e9073..05fa511 100644
--- a/media/java/android/media/MediaScannerConnection.java
+++ b/media/java/android/media/MediaScannerConnection.java
@@ -16,7 +16,7 @@
 
 package android.media;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/media/java/android/media/RouteDiscoveryPreference.aidl
similarity index 94%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to media/java/android/media/RouteDiscoveryPreference.aidl
index fb5d836..898eb39 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/media/java/android/media/RouteDiscoveryPreference.aidl
@@ -16,4 +16,4 @@
 
 package android.media;
 
-parcelable RouteSessionInfo;
+parcelable RouteDiscoveryPreference;
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
new file mode 100644
index 0000000..7ec1123
--- /dev/null
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A media route discovery preference  describing the kinds of routes that media router
+ * would like to discover and whether to perform active scanning.
+ *
+ * @see MediaRouter2#registerRouteCallback
+ */
+public final class RouteDiscoveryPreference implements Parcelable {
+    @NonNull
+    public static final Creator<RouteDiscoveryPreference> CREATOR =
+            new Creator<RouteDiscoveryPreference>() {
+                @Override
+                public RouteDiscoveryPreference createFromParcel(Parcel in) {
+                    return new RouteDiscoveryPreference(in);
+                }
+
+                @Override
+                public RouteDiscoveryPreference[] newArray(int size) {
+                    return new RouteDiscoveryPreference[size];
+                }
+            };
+
+    @NonNull
+    private final List<String> mPreferredFeatures;
+    private final boolean mActiveScan;
+    @Nullable
+    private final Bundle mExtras;
+
+    /**
+     * @hide
+     */
+    public static final RouteDiscoveryPreference EMPTY =
+            new Builder(Collections.emptyList(), false).build();
+
+    RouteDiscoveryPreference(@NonNull Builder builder) {
+        mPreferredFeatures = builder.mPreferredFeatures;
+        mActiveScan = builder.mActiveScan;
+        mExtras = builder.mExtras;
+    }
+
+    RouteDiscoveryPreference(@NonNull Parcel in) {
+        mPreferredFeatures = in.createStringArrayList();
+        mActiveScan = in.readBoolean();
+        mExtras = in.readBundle();
+    }
+
+    @NonNull
+    public List<String> getPreferredFeatures() {
+        return mPreferredFeatures;
+    }
+
+    public boolean isActiveScan() {
+        return mActiveScan;
+    }
+
+    /**
+     * @hide
+     */
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStringList(mPreferredFeatures);
+        dest.writeBoolean(mActiveScan);
+        dest.writeBundle(mExtras);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder()
+                .append("RouteDiscoveryRequest{ ")
+                .append("preferredFeatures={")
+                .append(String.join(", ", mPreferredFeatures))
+                .append("}")
+                .append(", activeScan=")
+                .append(mActiveScan)
+                .append(" }");
+
+        return result.toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof RouteDiscoveryPreference)) {
+            return false;
+        }
+        RouteDiscoveryPreference other = (RouteDiscoveryPreference) o;
+        return Objects.equals(mPreferredFeatures, other.mPreferredFeatures)
+                && mActiveScan == other.mActiveScan;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPreferredFeatures, mActiveScan);
+    }
+
+    /**
+     * Builder for {@link RouteDiscoveryPreference}.
+     */
+    public static final class Builder {
+        List<String> mPreferredFeatures;
+        boolean mActiveScan;
+        Bundle mExtras;
+
+        public Builder(@NonNull List<String> preferredFeatures, boolean activeScan) {
+            mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures,
+                    "preferredFeatures must not be null"));
+            mActiveScan = activeScan;
+        }
+
+        public Builder(@NonNull RouteDiscoveryPreference preference) {
+            Objects.requireNonNull(preference, "preference must not be null");
+
+            mPreferredFeatures = preference.getPreferredFeatures();
+            mActiveScan = preference.isActiveScan();
+            mExtras = preference.getExtras();
+        }
+
+        /**
+         * A constructor to combine all of the preferences into a single preference .
+         * It ignores extras of preferences.
+         *
+         * @hide
+         */
+        public Builder(@NonNull Collection<RouteDiscoveryPreference> preferences) {
+            Objects.requireNonNull(preferences, "preferences must not be null");
+
+            Set<String> routeFeatureSet = new HashSet<>();
+            mActiveScan = false;
+            for (RouteDiscoveryPreference preference : preferences) {
+                routeFeatureSet.addAll(preference.mPreferredFeatures);
+                mActiveScan |= preference.mActiveScan;
+            }
+            mPreferredFeatures = new ArrayList<>(routeFeatureSet);
+        }
+
+        /**
+         * Sets preferred route features to discover.
+         */
+        @NonNull
+        public Builder setPreferredFeatures(@NonNull List<String> preferredFeatures) {
+            mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures,
+                            "preferredFeatures must not be null"));
+            return this;
+        }
+
+        /**
+         * Sets if active scanning should be performed.
+         */
+        @NonNull
+        public Builder setActiveScan(boolean activeScan) {
+            mActiveScan = activeScan;
+            return this;
+        }
+
+        /**
+         * Sets the extras of the route.
+         * @hide
+         */
+        @NonNull
+        public Builder setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Builds the {@link RouteDiscoveryPreference}.
+         */
+        @NonNull
+        public RouteDiscoveryPreference build() {
+            return new RouteDiscoveryPreference(this);
+        }
+    }
+}
diff --git a/media/java/android/media/RouteSessionInfo.java b/media/java/android/media/RouteSessionInfo.java
deleted file mode 100644
index 2d7bc24..0000000
--- a/media/java/android/media/RouteSessionInfo.java
+++ /dev/null
@@ -1,464 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Describes a route session that is made when a media route is selected.
- * @hide
- */
-public class RouteSessionInfo implements Parcelable {
-    @NonNull
-    public static final Creator<RouteSessionInfo> CREATOR =
-            new Creator<RouteSessionInfo>() {
-                @Override
-                public RouteSessionInfo createFromParcel(Parcel in) {
-                    return new RouteSessionInfo(in);
-                }
-                @Override
-                public RouteSessionInfo[] newArray(int size) {
-                    return new RouteSessionInfo[size];
-                }
-            };
-
-    final int mSessionId;
-    final String mPackageName;
-    final String mControlCategory;
-    @Nullable
-    final String mProviderId;
-    final List<String> mSelectedRoutes;
-    final List<String> mSelectableRoutes;
-    final List<String> mDeselectableRoutes;
-    final List<String> mTransferrableRoutes;
-    @Nullable
-    final Bundle mControlHints;
-
-    RouteSessionInfo(@NonNull Builder builder) {
-        Objects.requireNonNull(builder, "builder must not be null.");
-
-        mSessionId = builder.mSessionId;
-        mPackageName = builder.mPackageName;
-        mControlCategory = builder.mControlCategory;
-        mProviderId = builder.mProviderId;
-
-        mSelectedRoutes = Collections.unmodifiableList(builder.mSelectedRoutes);
-        mSelectableRoutes = Collections.unmodifiableList(builder.mSelectableRoutes);
-        mDeselectableRoutes = Collections.unmodifiableList(builder.mDeselectableRoutes);
-        mTransferrableRoutes = Collections.unmodifiableList(builder.mTransferrableRoutes);
-
-        mControlHints = builder.mControlHints;
-    }
-
-    RouteSessionInfo(@NonNull Parcel src) {
-        Objects.requireNonNull(src, "src must not be null.");
-
-        mSessionId = src.readInt();
-        mPackageName = ensureString(src.readString());
-        mControlCategory = ensureString(src.readString());
-        mProviderId = src.readString();
-
-        mSelectedRoutes = ensureList(src.createStringArrayList());
-        mSelectableRoutes = ensureList(src.createStringArrayList());
-        mDeselectableRoutes = ensureList(src.createStringArrayList());
-        mTransferrableRoutes = ensureList(src.createStringArrayList());
-
-        mControlHints = src.readBundle();
-    }
-
-    private static String ensureString(String str) {
-        if (str != null) {
-            return str;
-        }
-        return "";
-    }
-
-    private static <T> List<T> ensureList(List<? extends T> list) {
-        if (list != null) {
-            return Collections.unmodifiableList(list);
-        }
-        return Collections.emptyList();
-    }
-
-    /**
-     * Gets non-unique session id (int) from unique session id (string).
-     * If the corresponding session id could not be generated, it will return null.
-     * @hide
-     */
-    @Nullable
-    public static Integer getSessionId(@NonNull String uniqueSessionId) {
-        int lastIndexOfSeparator = uniqueSessionId.lastIndexOf("/");
-        if (lastIndexOfSeparator == -1 || lastIndexOfSeparator + 1 >= uniqueSessionId.length()) {
-            return null;
-        }
-
-        String integerString = uniqueSessionId.substring(lastIndexOfSeparator + 1);
-        if (TextUtils.isEmpty(integerString)) {
-            return null;
-        }
-
-        try {
-            return Integer.parseInt(integerString);
-        } catch (NumberFormatException ex) {
-            return null;
-        }
-    }
-
-    /**
-     * Gets provider ID (string) from unique session id (string).
-     * If the corresponding provider ID could not be generated, it will return null.
-     * @hide
-     *
-     * TODO: This logic seems error-prone. Consider to use long uniqueId.
-     */
-    @Nullable
-    public static String getProviderId(@NonNull String uniqueSessionId) {
-        int lastIndexOfSeparator = uniqueSessionId.lastIndexOf("/");
-        if (lastIndexOfSeparator == -1) {
-            return null;
-        }
-
-        String result = uniqueSessionId.substring(0, lastIndexOfSeparator);
-        if (TextUtils.isEmpty(result)) {
-            return null;
-        }
-        return result;
-    }
-
-    /**
-     * Returns whether the session info is valid or not
-     */
-    public boolean isValid() {
-        return !TextUtils.isEmpty(mPackageName)
-                && !TextUtils.isEmpty(mControlCategory)
-                && mSelectedRoutes.size() > 0;
-    }
-
-    /**
-     * Gets the id of the session
-     */
-    @NonNull
-    public int getSessionId() {
-        return mSessionId;
-    }
-
-    /**
-     * Gets the client package name of the session
-     */
-    @NonNull
-    public String getPackageName() {
-        return mPackageName;
-    }
-
-    /**
-     * Gets the control category of the session.
-     * Routes that don't support the category can't be added to the session.
-     */
-    @NonNull
-    public String getControlCategory() {
-        return mControlCategory;
-    }
-
-    /**
-     * Gets the provider id of the session.
-     * @hide
-     */
-    @Nullable
-    public String getProviderId() {
-        return mProviderId;
-    }
-
-    /**
-     * Gets the unique id of the session.
-     * @hide
-     */
-    @NonNull
-    public String getUniqueSessionId() {
-        StringBuilder sessionIdBuilder = new StringBuilder()
-                .append(mProviderId)
-                .append("/")
-                .append(mSessionId);
-        return sessionIdBuilder.toString();
-    }
-
-    /**
-     * Gets the list of ids of selected routes for the session. It shouldn't be empty.
-     */
-    @NonNull
-    public List<String> getSelectedRoutes() {
-        return mSelectedRoutes;
-    }
-
-    /**
-     * Gets the list of ids of selectable routes for the session.
-     */
-    @NonNull
-    public List<String> getSelectableRoutes() {
-        return mSelectableRoutes;
-    }
-
-    /**
-     * Gets the list of ids of deselectable routes for the session.
-     */
-    @NonNull
-    public List<String> getDeselectableRoutes() {
-        return mDeselectableRoutes;
-    }
-
-    /**
-     * Gets the list of ids of transferrable routes for the session.
-     */
-    @NonNull
-    public List<String> getTransferrableRoutes() {
-        return mTransferrableRoutes;
-    }
-
-    /**
-     * Gets the control hints
-     */
-    @Nullable
-    public Bundle getControlHints() {
-        return mControlHints;
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(mSessionId);
-        dest.writeString(mPackageName);
-        dest.writeString(mControlCategory);
-        dest.writeString(mProviderId);
-        dest.writeStringList(mSelectedRoutes);
-        dest.writeStringList(mSelectableRoutes);
-        dest.writeStringList(mDeselectableRoutes);
-        dest.writeStringList(mTransferrableRoutes);
-        dest.writeBundle(mControlHints);
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder result = new StringBuilder()
-                .append("RouteSessionInfo{ ")
-                .append("sessionId=").append(mSessionId)
-                .append(", controlCategory=").append(mControlCategory)
-                .append(", selectedRoutes={")
-                .append(String.join(",", mSelectedRoutes))
-                .append("}")
-                .append(", selectableRoutes={")
-                .append(String.join(",", mSelectableRoutes))
-                .append("}")
-                .append(", deselectableRoutes={")
-                .append(String.join(",", mDeselectableRoutes))
-                .append("}")
-                .append(", transferrableRoutes={")
-                .append(String.join(",", mTransferrableRoutes))
-                .append("}")
-                .append(" }");
-        return result.toString();
-    }
-
-    /**
-     * Builder class for {@link RouteSessionInfo}.
-     */
-    public static final class Builder {
-        final String mPackageName;
-        final int mSessionId;
-        final String mControlCategory;
-        String mProviderId;
-        final List<String> mSelectedRoutes;
-        final List<String> mSelectableRoutes;
-        final List<String> mDeselectableRoutes;
-        final List<String> mTransferrableRoutes;
-        Bundle mControlHints;
-
-        public Builder(int sessionId, @NonNull String packageName,
-                @NonNull String controlCategory) {
-            mSessionId = sessionId;
-            mPackageName = Objects.requireNonNull(packageName, "packageName must not be null");
-            mControlCategory = Objects.requireNonNull(controlCategory,
-                    "controlCategory must not be null");
-
-            mSelectedRoutes = new ArrayList<>();
-            mSelectableRoutes = new ArrayList<>();
-            mDeselectableRoutes = new ArrayList<>();
-            mTransferrableRoutes = new ArrayList<>();
-        }
-
-        public Builder(RouteSessionInfo sessionInfo) {
-            mSessionId = sessionInfo.mSessionId;
-            mPackageName = sessionInfo.mPackageName;
-            mControlCategory = sessionInfo.mControlCategory;
-            mProviderId = sessionInfo.mProviderId;
-
-            mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes);
-            mSelectableRoutes = new ArrayList<>(sessionInfo.mSelectableRoutes);
-            mDeselectableRoutes = new ArrayList<>(sessionInfo.mDeselectableRoutes);
-            mTransferrableRoutes = new ArrayList<>(sessionInfo.mTransferrableRoutes);
-
-            mControlHints = sessionInfo.mControlHints;
-        }
-
-        /**
-         * Sets the provider id of the session.
-         */
-        @NonNull
-        public Builder setProviderId(String providerId) {
-            mProviderId = providerId;
-            return this;
-        }
-
-        /**
-         * Clears the selected routes.
-         */
-        @NonNull
-        public Builder clearSelectedRoutes() {
-            mSelectedRoutes.clear();
-            return this;
-        }
-
-        /**
-         * Adds a route to the selected routes.
-         */
-        @NonNull
-        public Builder addSelectedRoute(@NonNull String routeId) {
-            mSelectedRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
-            return this;
-        }
-
-        /**
-         * Removes a route from the selected routes.
-         */
-        @NonNull
-        public Builder removeSelectedRoute(@NonNull String routeId) {
-            mSelectedRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
-            return this;
-        }
-
-        /**
-         * Clears the selectable routes.
-         */
-        @NonNull
-        public Builder clearSelectableRoutes() {
-            mSelectableRoutes.clear();
-            return this;
-        }
-
-        /**
-         * Adds a route to the selectable routes.
-         */
-        @NonNull
-        public Builder addSelectableRoute(@NonNull String routeId) {
-            mSelectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
-            return this;
-        }
-
-        /**
-         * Removes a route from the selectable routes.
-         */
-        @NonNull
-        public Builder removeSelectableRoute(@NonNull String routeId) {
-            mSelectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
-            return this;
-        }
-
-        /**
-         * Clears the deselectable routes.
-         */
-        @NonNull
-        public Builder clearDeselectableRoutes() {
-            mDeselectableRoutes.clear();
-            return this;
-        }
-
-        /**
-         * Adds a route to the deselectable routes.
-         */
-        @NonNull
-        public Builder addDeselectableRoute(@NonNull String routeId) {
-            mDeselectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
-            return this;
-        }
-
-        /**
-         * Removes a route from the deselectable routes.
-         */
-        @NonNull
-        public Builder removeDeselectableRoute(@NonNull String routeId) {
-            mDeselectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
-            return this;
-        }
-
-        /**
-         * Clears the transferrable routes.
-         */
-        @NonNull
-        public Builder clearTransferrableRoutes() {
-            mTransferrableRoutes.clear();
-            return this;
-        }
-
-        /**
-         * Adds a route to the transferrable routes.
-         */
-        @NonNull
-        public Builder addTransferrableRoute(@NonNull String routeId) {
-            mTransferrableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
-            return this;
-        }
-
-        /**
-         * Removes a route from the transferrable routes.
-         */
-        @NonNull
-        public Builder removeTransferrableRoute(@NonNull String routeId) {
-            mTransferrableRoutes.remove(
-                    Objects.requireNonNull(routeId, "routeId must not be null"));
-            return this;
-        }
-
-        /**
-         * Sets control hints.
-         */
-        @NonNull
-        public Builder setControlHints(@Nullable Bundle controlHints) {
-            mControlHints = controlHints;
-            return this;
-        }
-
-        /**
-         * Builds a route session info.
-         */
-        @NonNull
-        public RouteSessionInfo build() {
-            return new RouteSessionInfo(this);
-        }
-    }
-}
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/media/java/android/media/RoutingSessionInfo.aidl
similarity index 95%
rename from media/java/android/media/RouteSessionInfo.aidl
rename to media/java/android/media/RoutingSessionInfo.aidl
index fb5d836..7b8e3d9 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/media/java/android/media/RoutingSessionInfo.aidl
@@ -16,4 +16,4 @@
 
 package android.media;
 
-parcelable RouteSessionInfo;
+parcelable RoutingSessionInfo;
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
new file mode 100644
index 0000000..96acf6c
--- /dev/null
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -0,0 +1,529 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Describes a routing session which is created when a media route is selected.
+ * @hide
+ */
+public final class RoutingSessionInfo implements Parcelable {
+    @NonNull
+    public static final Creator<RoutingSessionInfo> CREATOR =
+            new Creator<RoutingSessionInfo>() {
+                @Override
+                public RoutingSessionInfo createFromParcel(Parcel in) {
+                    return new RoutingSessionInfo(in);
+                }
+                @Override
+                public RoutingSessionInfo[] newArray(int size) {
+                    return new RoutingSessionInfo[size];
+                }
+            };
+
+    private static final String TAG = "RoutingSessionInfo";
+
+    final String mId;
+    final String mClientPackageName;
+    final String mRouteFeature;
+    @Nullable
+    final String mProviderId;
+    final List<String> mSelectedRoutes;
+    final List<String> mSelectableRoutes;
+    final List<String> mDeselectableRoutes;
+    final List<String> mTransferrableRoutes;
+    @Nullable
+    final Bundle mControlHints;
+
+    RoutingSessionInfo(@NonNull Builder builder) {
+        Objects.requireNonNull(builder, "builder must not be null.");
+
+        mId = builder.mId;
+        mClientPackageName = builder.mClientPackageName;
+        mRouteFeature = builder.mRouteFeature;
+        mProviderId = builder.mProviderId;
+
+        // TODO: Needs to check that the routes already have unique IDs.
+        mSelectedRoutes = Collections.unmodifiableList(
+                convertToUniqueRouteIds(builder.mSelectedRoutes));
+        mSelectableRoutes = Collections.unmodifiableList(
+                convertToUniqueRouteIds(builder.mSelectableRoutes));
+        mDeselectableRoutes = Collections.unmodifiableList(
+                convertToUniqueRouteIds(builder.mDeselectableRoutes));
+        mTransferrableRoutes = Collections.unmodifiableList(
+                convertToUniqueRouteIds(builder.mTransferrableRoutes));
+
+        mControlHints = builder.mControlHints;
+    }
+
+    RoutingSessionInfo(@NonNull Parcel src) {
+        Objects.requireNonNull(src, "src must not be null.");
+
+        mId = ensureString(src.readString());
+        mClientPackageName = ensureString(src.readString());
+        mRouteFeature = ensureString(src.readString());
+        mProviderId = src.readString();
+
+        mSelectedRoutes = ensureList(src.createStringArrayList());
+        mSelectableRoutes = ensureList(src.createStringArrayList());
+        mDeselectableRoutes = ensureList(src.createStringArrayList());
+        mTransferrableRoutes = ensureList(src.createStringArrayList());
+
+        mControlHints = src.readBundle();
+    }
+
+    private static String ensureString(String str) {
+        if (str != null) {
+            return str;
+        }
+        return "";
+    }
+
+    private static <T> List<T> ensureList(List<? extends T> list) {
+        if (list != null) {
+            return Collections.unmodifiableList(list);
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Gets the id of the session. The sessions which are given by {@link MediaRouter2} will have
+     * unique IDs.
+     * <p>
+     * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method
+     * can be different from what was set in {@link MediaRoute2ProviderService}.
+     *
+     * @see Builder#Builder(String, String, String)
+     */
+    @NonNull
+    public String getId() {
+        if (mProviderId != null) {
+            return MediaRouter2Utils.toUniqueId(mProviderId, mId);
+        } else {
+            return mId;
+        }
+    }
+
+    /**
+     * Gets the original id set by {@link Builder#Builder(String, String, String)}.
+     * @hide
+     */
+    @NonNull
+    public String getOriginalId() {
+        return mId;
+    }
+
+    /**
+     * Gets the client package name of the session
+     */
+    @NonNull
+    public String getClientPackageName() {
+        return mClientPackageName;
+    }
+
+    /**
+     * Gets the route feature of the session.
+     * Routes that don't have the feature can't be selected into the session.
+     */
+    @NonNull
+    public String getRouteFeature() {
+        return mRouteFeature;
+    }
+
+    /**
+     * Gets the provider id of the session.
+     * @hide
+     */
+    @Nullable
+    public String getProviderId() {
+        return mProviderId;
+    }
+
+    /**
+     * Gets the list of ids of selected routes for the session. It shouldn't be empty.
+     */
+    @NonNull
+    public List<String> getSelectedRoutes() {
+        return mSelectedRoutes;
+    }
+
+    /**
+     * Gets the list of ids of selectable routes for the session.
+     */
+    @NonNull
+    public List<String> getSelectableRoutes() {
+        return mSelectableRoutes;
+    }
+
+    /**
+     * Gets the list of ids of deselectable routes for the session.
+     */
+    @NonNull
+    public List<String> getDeselectableRoutes() {
+        return mDeselectableRoutes;
+    }
+
+    /**
+     * Gets the list of ids of transferrable routes for the session.
+     */
+    @NonNull
+    public List<String> getTransferrableRoutes() {
+        return mTransferrableRoutes;
+    }
+
+    /**
+     * Gets the control hints
+     */
+    @Nullable
+    public Bundle getControlHints() {
+        return mControlHints;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mId);
+        dest.writeString(mClientPackageName);
+        dest.writeString(mRouteFeature);
+        dest.writeString(mProviderId);
+        dest.writeStringList(mSelectedRoutes);
+        dest.writeStringList(mSelectableRoutes);
+        dest.writeStringList(mDeselectableRoutes);
+        dest.writeStringList(mTransferrableRoutes);
+        dest.writeBundle(mControlHints);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof RoutingSessionInfo)) {
+            return false;
+        }
+
+        RoutingSessionInfo other = (RoutingSessionInfo) obj;
+        return Objects.equals(mId, other.mId)
+                && Objects.equals(mClientPackageName, other.mClientPackageName)
+                && Objects.equals(mRouteFeature, other.mRouteFeature)
+                && Objects.equals(mProviderId, other.mProviderId)
+                && Objects.equals(mSelectedRoutes, other.mSelectedRoutes)
+                && Objects.equals(mSelectableRoutes, other.mSelectableRoutes)
+                && Objects.equals(mDeselectableRoutes, other.mDeselectableRoutes)
+                && Objects.equals(mTransferrableRoutes, other.mTransferrableRoutes);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mId, mClientPackageName, mRouteFeature, mProviderId,
+                mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferrableRoutes);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder()
+                .append("RoutingSessionInfo{ ")
+                .append("sessionId=").append(mId)
+                .append(", routeFeature=").append(mRouteFeature)
+                .append(", selectedRoutes={")
+                .append(String.join(",", mSelectedRoutes))
+                .append("}")
+                .append(", selectableRoutes={")
+                .append(String.join(",", mSelectableRoutes))
+                .append("}")
+                .append(", deselectableRoutes={")
+                .append(String.join(",", mDeselectableRoutes))
+                .append("}")
+                .append(", transferrableRoutes={")
+                .append(String.join(",", mTransferrableRoutes))
+                .append("}")
+                .append(" }");
+        return result.toString();
+    }
+
+    private List<String> convertToUniqueRouteIds(@NonNull List<String> routeIds) {
+        if (routeIds == null) {
+            Log.w(TAG, "routeIds is null. Returning an empty list");
+            return Collections.emptyList();
+        }
+
+        // mProviderId can be null if not set. Return the original list for this case.
+        if (mProviderId == null) {
+            return routeIds;
+        }
+
+        List<String> result = new ArrayList<>();
+        for (String routeId : routeIds) {
+            result.add(MediaRouter2Utils.toUniqueId(mProviderId, routeId));
+        }
+        return result;
+    }
+
+    /**
+     * Builder class for {@link RoutingSessionInfo}.
+     */
+    public static final class Builder {
+        final String mId;
+        final String mClientPackageName;
+        final String mRouteFeature;
+        String mProviderId;
+        final List<String> mSelectedRoutes;
+        final List<String> mSelectableRoutes;
+        final List<String> mDeselectableRoutes;
+        final List<String> mTransferrableRoutes;
+        Bundle mControlHints;
+
+        /**
+         * Constructor for builder to create {@link RoutingSessionInfo}.
+         * <p>
+         * In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of
+         * {@link RoutingSessionInfo#getId()} can be different from what was set in
+         * {@link MediaRoute2ProviderService}.
+         * </p>
+         *
+         * @param id ID of the session. Must not be empty.
+         * @param clientPackageName package name of the client app which uses this session.
+         *                          If is is unknown, then just use an empty string.
+         * @param routeFeature the route feature of session. Must not be empty.
+         * @see MediaRoute2Info#getId()
+         */
+        public Builder(@NonNull String id, @NonNull String clientPackageName,
+                @NonNull String routeFeature) {
+            if (TextUtils.isEmpty(id)) {
+                throw new IllegalArgumentException("id must not be empty");
+            }
+            Objects.requireNonNull(clientPackageName, "clientPackageName must not be null");
+            if (TextUtils.isEmpty(routeFeature)) {
+                throw new IllegalArgumentException("routeFeature must not be empty");
+            }
+
+            mId = id;
+            mClientPackageName = clientPackageName;
+            mRouteFeature = routeFeature;
+            mSelectedRoutes = new ArrayList<>();
+            mSelectableRoutes = new ArrayList<>();
+            mDeselectableRoutes = new ArrayList<>();
+            mTransferrableRoutes = new ArrayList<>();
+        }
+
+        /**
+         * Constructor for builder to create {@link RoutingSessionInfo} with
+         * existing {@link RoutingSessionInfo} instance.
+         *
+         * @param sessionInfo the existing instance to copy data from.
+         */
+        public Builder(@NonNull RoutingSessionInfo sessionInfo) {
+            Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+
+            mId = sessionInfo.mId;
+            mClientPackageName = sessionInfo.mClientPackageName;
+            mRouteFeature = sessionInfo.mRouteFeature;
+            mProviderId = sessionInfo.mProviderId;
+
+            mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes);
+            mSelectableRoutes = new ArrayList<>(sessionInfo.mSelectableRoutes);
+            mDeselectableRoutes = new ArrayList<>(sessionInfo.mDeselectableRoutes);
+            mTransferrableRoutes = new ArrayList<>(sessionInfo.mTransferrableRoutes);
+
+            mControlHints = sessionInfo.mControlHints;
+        }
+
+        /**
+         * Sets the provider ID of the session.
+         *
+         * @hide
+         */
+        @NonNull
+        public Builder setProviderId(@NonNull String providerId) {
+            if (TextUtils.isEmpty(providerId)) {
+                throw new IllegalArgumentException("providerId must not be empty");
+            }
+            mProviderId = providerId;
+            return this;
+        }
+
+        /**
+         * Clears the selected routes.
+         */
+        @NonNull
+        public Builder clearSelectedRoutes() {
+            mSelectedRoutes.clear();
+            return this;
+        }
+
+        /**
+         * Adds a route to the selected routes. The {@code routeId} must not be empty.
+         */
+        @NonNull
+        public Builder addSelectedRoute(@NonNull String routeId) {
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mSelectedRoutes.add(routeId);
+            return this;
+        }
+
+        /**
+         * Removes a route from the selected routes. The {@code routeId} must not be empty.
+         */
+        @NonNull
+        public Builder removeSelectedRoute(@NonNull String routeId) {
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mSelectedRoutes.remove(routeId);
+            return this;
+        }
+
+        /**
+         * Clears the selectable routes.
+         */
+        @NonNull
+        public Builder clearSelectableRoutes() {
+            mSelectableRoutes.clear();
+            return this;
+        }
+
+        /**
+         * Adds a route to the selectable routes. The {@code routeId} must not be empty.
+         */
+        @NonNull
+        public Builder addSelectableRoute(@NonNull String routeId) {
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mSelectableRoutes.add(routeId);
+            return this;
+        }
+
+        /**
+         * Removes a route from the selectable routes. The {@code routeId} must not be empty.
+         */
+        @NonNull
+        public Builder removeSelectableRoute(@NonNull String routeId) {
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mSelectableRoutes.remove(routeId);
+            return this;
+        }
+
+        /**
+         * Clears the deselectable routes.
+         */
+        @NonNull
+        public Builder clearDeselectableRoutes() {
+            mDeselectableRoutes.clear();
+            return this;
+        }
+
+        /**
+         * Adds a route to the deselectable routes. The {@code routeId} must not be empty.
+         */
+        @NonNull
+        public Builder addDeselectableRoute(@NonNull String routeId) {
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mDeselectableRoutes.add(routeId);
+            return this;
+        }
+
+        /**
+         * Removes a route from the deselectable routes. The {@code routeId} must not be empty.
+         */
+        @NonNull
+        public Builder removeDeselectableRoute(@NonNull String routeId) {
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mDeselectableRoutes.remove(routeId);
+            return this;
+        }
+
+        /**
+         * Clears the transferrable routes.
+         */
+        @NonNull
+        public Builder clearTransferrableRoutes() {
+            mTransferrableRoutes.clear();
+            return this;
+        }
+
+        /**
+         * Adds a route to the transferrable routes. The {@code routeId} must not be empty.
+         */
+        @NonNull
+        public Builder addTransferrableRoute(@NonNull String routeId) {
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mTransferrableRoutes.add(routeId);
+            return this;
+        }
+
+        /**
+         * Removes a route from the transferrable routes. The {@code routeId} must not be empty.
+         */
+        @NonNull
+        public Builder removeTransferrableRoute(@NonNull String routeId) {
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mTransferrableRoutes.remove(routeId);
+            return this;
+        }
+
+        /**
+         * Sets control hints.
+         */
+        @NonNull
+        public Builder setControlHints(@Nullable Bundle controlHints) {
+            mControlHints = controlHints;
+            return this;
+        }
+
+        /**
+         * Builds a routing session info.
+         *
+         * @throws IllegalArgumentException if no selected routes are added.
+         */
+        @NonNull
+        public RoutingSessionInfo build() {
+            if (mSelectedRoutes.isEmpty()) {
+                throw new IllegalArgumentException("selectedRoutes must not be empty");
+            }
+            return new RoutingSessionInfo(this);
+        }
+    }
+}
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index aff7257..aece39d 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -940,16 +940,15 @@
         /**
          * Called when a media key event is dispatched through the media session service. The
          * session token can be {@link null} if the framework has sent the media key event to the
-         * media button receiver to revive the media app's playback.
-         *
-         * the session is dead when , but the framework sent
+         * media button receiver to revive the media app's playback after the corresponding session
+         * is released.
          *
          * @param event Dispatched media key event.
          * @param packageName Package
          * @param sessionToken The media session's token. Can be {@code null}.
          */
         default void onMediaKeyEventDispatched(@NonNull KeyEvent event, @NonNull String packageName,
-                @NonNull MediaSession.Token sessionToken) { }
+                @Nullable MediaSession.Token sessionToken) { }
     }
 
     /**
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 56e5566..118f65c 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -22,9 +22,10 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.media.AudioFormat;
 import android.os.Handler;
@@ -75,7 +76,9 @@
             value = {
                 RECOGNITION_FLAG_NONE,
                 RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO,
-                RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
+                RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS,
+                RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION,
+                    RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION,
             })
     public @interface RecognitionFlags {}
 
@@ -100,11 +103,34 @@
      * triggers after a call to {@link #startRecognition(int)}, if the model
      * triggers multiple times.
      * When this isn't specified, the default behavior is to stop recognition once the
-     * trigger happenss, till the caller starts recognition again.
+     * trigger happens, till the caller starts recognition again.
      */
     public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2;
 
     /**
+     * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+     * if the underlying recognition should use AEC.
+     * This capability may or may not be supported by the system, and support can be queried
+     * by calling {@link SoundTriggerManager#getModuleProperties()} and checking
+     * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for
+     * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_ECHO_CANCELLATION}.
+     * If this flag is passed without the audio capability supported, there will be no audio effect
+     * applied.
+     */
+    public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 0x4;
+
+    /**
+     * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+     * if the underlying recognition should use noise suppression.
+     * This capability may or may not be supported by the system, and support can be queried
+     * by calling {@link SoundTriggerManager#getModuleProperties()} and checking
+     * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for
+     * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_NOISE_SUPPRESSION}. If this flag
+     * is passed without the audio capability supported, there will be no audio effect applied.
+     */
+    public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8;
+
+    /**
      * Additional payload for {@link Callback#onDetected}.
      */
     public static class EventPayload {
@@ -267,11 +293,20 @@
 
         boolean allowMultipleTriggers =
                 (recognitionFlags & RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0;
-        int status = STATUS_OK;
+
+        int audioCapabilities = 0;
+        if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) {
+            audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+        }
+        if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) {
+            audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+        }
+
+        int status;
         try {
             status = mSoundTriggerService.startRecognition(new ParcelUuid(mSoundModelId),
                     mRecognitionCallback, new RecognitionConfig(captureTriggerAudio,
-                        allowMultipleTriggers, null, null));
+                        allowMultipleTriggers, null, null, audioCapabilities));
         } catch (RemoteException e) {
             return false;
         }
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 61b3e76..938ffcd 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -23,7 +23,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
 import android.hardware.soundtrigger.ModelParams;
@@ -428,8 +428,7 @@
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
     public int setParameter(@Nullable UUID soundModelId,
-            @ModelParams int modelParam, int value)
-            throws UnsupportedOperationException, IllegalArgumentException {
+            @ModelParams int modelParam, int value) {
         try {
             return mSoundTriggerService.setParameter(new ParcelUuid(soundModelId), modelParam,
                     value);
@@ -449,15 +448,10 @@
      * @param soundModelId UUID of model to get parameter
      * @param modelParam   {@link ModelParams}
      * @return value of parameter
-     * @throws UnsupportedOperationException if hal or model do not support this API.
-     *         {@link SoundTriggerManager#queryParameter} should be checked first.
-     * @throws IllegalArgumentException if invalid model handle or parameter is passed.
-     *         {@link SoundTriggerManager#queryParameter} should be checked first.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
     public int getParameter(@NonNull UUID soundModelId,
-            @ModelParams int modelParam)
-            throws UnsupportedOperationException, IllegalArgumentException {
+            @ModelParams int modelParam) {
         try {
             return mSoundTriggerService.getParameter(new ParcelUuid(soundModelId), modelParam);
         } catch (RemoteException e) {
@@ -479,8 +473,7 @@
     public ModelParamRange queryParameter(@Nullable UUID soundModelId,
             @ModelParams int modelParam) {
         try {
-            return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId),
-                    modelParam);
+            return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId), modelParam);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl b/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl
new file mode 100644
index 0000000..97a8849
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * AudioCapabilities supported by the implemented HAL driver.
+ * @hide
+ */
+@Backing(type="int")
+enum AudioCapabilities {
+    /**
+     * If set the underlying module supports AEC.
+     */
+    ECHO_CANCELLATION = 1 << 0,
+    /**
+     * If set, the underlying module supports noise suppression.
+     */
+    NOISE_SUPPRESSION = 1 << 1,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
index c7642e8..5c0eeb1 100644
--- a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
@@ -28,6 +28,12 @@
     /* Configuration for each key phrase. */
     PhraseRecognitionExtra[] phraseRecognitionExtras;
 
+    /**
+     * Bit field encoding of the AudioCapabilities
+     * supported by the firmware.
+     */
+    int audioCapabilities;
+
     /** Opaque capture configuration data. */
     byte[] data;
 }
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
index 1a3b402..9c56e7b 100644
--- a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
@@ -30,6 +30,14 @@
      * Unique implementation ID. The UUID must change with each version of
        the engine implementation */
     String     uuid;
+    /**
+     * String naming the architecture used for running the supported models.
+     * (eg. a platform running models on a DSP could implement this string to convey the DSP
+     * architecture used)
+     * This property is supported for soundtrigger HAL v2.3 and above.
+     * If running a previous version, the string will be empty.
+     */
+    String supportedModelArch;
     /** Maximum number of concurrent sound models loaded */
     int maxSoundModels;
     /** Maximum number of key phrases */
@@ -50,4 +58,11 @@
      * Rated power consumption when detection is active with TDB
      * silence/sound/speech ratio */
     int powerConsumptionMw;
+    /**
+     * Bit field encoding of the AudioCapabilities
+     * supported by the firmware.
+     * This property is supported for soundtrigger HAL v2.3 and above.
+     * If running a previous version, this value will be 0.
+     */
+    int audioCapabilities;
 }
diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/java/android/media/soundtrigger_middleware/Status.aidl
index d8f9d8f..5d082e2 100644
--- a/media/java/android/media/soundtrigger_middleware/Status.aidl
+++ b/media/java/android/media/soundtrigger_middleware/Status.aidl
@@ -26,4 +26,6 @@
     RESOURCE_CONTENTION = 1,
     /** Operation is not supported in this implementation. This is a permanent condition. */
     OPERATION_NOT_SUPPORTED = 2,
+    /** Temporary lack of permission. */
+    TEMPORARY_PERMISSION_DENIED = 3,
 }
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 1b9cac0..377b2bc 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -20,7 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.StringRes;
 import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 5c11ed9b..7fbb337 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -22,9 +22,9 @@
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityManager;
 import android.app.Service;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.PixelFormat;
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index ab7bbca..5e0b1ea 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -62,6 +62,8 @@
     private final int mAudioChannelCount;
     private final int mAudioSampleRate;
     private final boolean mAudioDescription;
+    private final boolean mHardOfHearing;
+    private final boolean mSpokenSubtitle;
     private final int mVideoWidth;
     private final int mVideoHeight;
     private final float mVideoFrameRate;
@@ -72,8 +74,9 @@
 
     private TvTrackInfo(int type, String id, String language, CharSequence description,
             boolean encrypted, int audioChannelCount, int audioSampleRate, boolean audioDescription,
-            int videoWidth, int videoHeight, float videoFrameRate, float videoPixelAspectRatio,
-            byte videoActiveFormatDescription, Bundle extra) {
+            boolean hardOfHearing, boolean spokenSubtitle, int videoWidth, int videoHeight,
+            float videoFrameRate, float videoPixelAspectRatio, byte videoActiveFormatDescription,
+            Bundle extra) {
         mType = type;
         mId = id;
         mLanguage = language;
@@ -82,6 +85,8 @@
         mAudioChannelCount = audioChannelCount;
         mAudioSampleRate = audioSampleRate;
         mAudioDescription = audioDescription;
+        mHardOfHearing = hardOfHearing;
+        mSpokenSubtitle = spokenSubtitle;
         mVideoWidth = videoWidth;
         mVideoHeight = videoHeight;
         mVideoFrameRate = videoFrameRate;
@@ -99,6 +104,8 @@
         mAudioChannelCount = in.readInt();
         mAudioSampleRate = in.readInt();
         mAudioDescription = in.readInt() != 0;
+        mHardOfHearing = in.readInt() != 0;
+        mSpokenSubtitle = in.readInt() != 0;
         mVideoWidth = in.readInt();
         mVideoHeight = in.readInt();
         mVideoFrameRate = in.readFloat();
@@ -192,6 +199,39 @@
     }
 
     /**
+     * Returns {@code true} if the track is intended for people with hearing impairment, {@code
+     * false} otherwise. Valid only for {@link #TYPE_AUDIO} and {@link #TYPE_SUBTITLE} tracks.
+     *
+     * <p>For example of broadcast, hard of hearing information may be referred to broadcast
+     * standard (e.g. ISO 639 Language Descriptor of ISO/IEC 13818-1, Supplementary Audio Language
+     * Descriptor, AC-3 Descriptor, Enhanced AC-3 Descriptor, AAC Descriptor of ETSI EN 300 468).
+     *
+     * @throws IllegalStateException if not called on an audio track or a subtitle track
+     */
+    public boolean isHardOfHearing() {
+        if (mType != TYPE_AUDIO && mType != TYPE_SUBTITLE) {
+            throw new IllegalStateException("Not an audio or a subtitle track");
+        }
+        return mHardOfHearing;
+    }
+
+    /**
+     * Returns {@code true} if the track is a spoken subtitle for people with visual impairment,
+     * {@code false} otherwise. Valid only for {@link #TYPE_AUDIO} tracks.
+     *
+     * <p>For example of broadcast, spoken subtitle information may be referred to broadcast
+     * standard (e.g. Supplementary Audio Language Descriptor of ETSI EN 300 468).
+     *
+     * @throws IllegalStateException if not called on an audio track
+     */
+    public boolean isSpokenSubtitle() {
+        if (mType != TYPE_AUDIO) {
+            throw new IllegalStateException("Not an audio track");
+        }
+        return mSpokenSubtitle;
+    }
+
+    /**
      * Returns the width of the video, in the unit of pixels. Valid only for {@link #TYPE_VIDEO}
      * tracks.
      *
@@ -278,7 +318,8 @@
      * @param flags The flags used for parceling.
      */
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        Preconditions.checkNotNull(dest);
         dest.writeInt(mType);
         dest.writeString(mId);
         dest.writeString(mLanguage);
@@ -287,6 +328,8 @@
         dest.writeInt(mAudioChannelCount);
         dest.writeInt(mAudioSampleRate);
         dest.writeInt(mAudioDescription ? 1 : 0);
+        dest.writeInt(mHardOfHearing ? 1 : 0);
+        dest.writeInt(mSpokenSubtitle ? 1 : 0);
         dest.writeInt(mVideoWidth);
         dest.writeInt(mVideoHeight);
         dest.writeFloat(mVideoFrameRate);
@@ -310,6 +353,7 @@
         if (!TextUtils.equals(mId, obj.mId) || mType != obj.mType
                 || !TextUtils.equals(mLanguage, obj.mLanguage)
                 || !TextUtils.equals(mDescription, obj.mDescription)
+                || mEncrypted != obj.mEncrypted
                 || !Objects.equals(mExtra, obj.mExtra)) {
             return false;
         }
@@ -318,7 +362,9 @@
             case TYPE_AUDIO:
                 return mAudioChannelCount == obj.mAudioChannelCount
                         && mAudioSampleRate == obj.mAudioSampleRate
-                        && mAudioDescription == obj.mAudioDescription;
+                        && mAudioDescription == obj.mAudioDescription
+                        && mHardOfHearing == obj.mHardOfHearing
+                        && mSpokenSubtitle == obj.mSpokenSubtitle;
 
             case TYPE_VIDEO:
                 return mVideoWidth == obj.mVideoWidth
@@ -326,6 +372,9 @@
                         && mVideoFrameRate == obj.mVideoFrameRate
                         && mVideoPixelAspectRatio == obj.mVideoPixelAspectRatio
                         && mVideoActiveFormatDescription == obj.mVideoActiveFormatDescription;
+
+            case TYPE_SUBTITLE:
+                return mHardOfHearing == obj.mHardOfHearing;
         }
 
         return true;
@@ -339,11 +388,13 @@
     public static final @android.annotation.NonNull Parcelable.Creator<TvTrackInfo> CREATOR =
             new Parcelable.Creator<TvTrackInfo>() {
                 @Override
+                @NonNull
                 public TvTrackInfo createFromParcel(Parcel in) {
                     return new TvTrackInfo(in);
                 }
 
                 @Override
+                @NonNull
                 public TvTrackInfo[] newArray(int size) {
                     return new TvTrackInfo[size];
                 }
@@ -361,6 +412,8 @@
         private int mAudioChannelCount;
         private int mAudioSampleRate;
         private boolean mAudioDescription;
+        private boolean mHardOfHearing;
+        private boolean mSpokenSubtitle;
         private int mVideoWidth;
         private int mVideoHeight;
         private float mVideoFrameRate;
@@ -394,7 +447,9 @@
          *
          * @param language The language string encoded by either ISO 639-1 or ISO 639-2/T.
          */
-        public final Builder setLanguage(String language) {
+        @NonNull
+        public  Builder setLanguage(@NonNull String language) {
+            Preconditions.checkNotNull(language);
             mLanguage = language;
             return this;
         }
@@ -404,7 +459,9 @@
          *
          * @param description The user readable description.
          */
-        public final Builder setDescription(CharSequence description) {
+        @NonNull
+        public  Builder setDescription(@NonNull CharSequence description) {
+            Preconditions.checkNotNull(description);
             mDescription = description;
             return this;
         }
@@ -429,7 +486,8 @@
          * @param audioChannelCount The audio channel count.
          * @throws IllegalStateException if not called on an audio track
          */
-        public final Builder setAudioChannelCount(int audioChannelCount) {
+        @NonNull
+        public Builder setAudioChannelCount(int audioChannelCount) {
             if (mType != TYPE_AUDIO) {
                 throw new IllegalStateException("Not an audio track");
             }
@@ -444,7 +502,8 @@
          * @param audioSampleRate The audio sample rate.
          * @throws IllegalStateException if not called on an audio track
          */
-        public final Builder setAudioSampleRate(int audioSampleRate) {
+        @NonNull
+        public Builder setAudioSampleRate(int audioSampleRate) {
             if (mType != TYPE_AUDIO) {
                 throw new IllegalStateException("Not an audio track");
             }
@@ -474,13 +533,54 @@
         }
 
         /**
+         * Sets the hard of hearing attribute of the track. Valid only for {@link #TYPE_AUDIO} and
+         * {@link #TYPE_SUBTITLE} tracks.
+         *
+         * <p>For example of broadcast, hard of hearing information may be referred to broadcast
+         * standard (e.g. ISO 639 Language Descriptor of ISO/IEC 13818-1, Supplementary Audio
+         * Language Descriptor, AC-3 Descriptor, Enhanced AC-3 Descriptor, AAC Descriptor of ETSI EN
+         * 300 468).
+         *
+         * @param hardOfHearing The hard of hearing attribute of the track.
+         * @throws IllegalStateException if not called on an audio track or a subtitle track
+         */
+        @NonNull
+        public Builder setHardOfHearing(boolean hardOfHearing) {
+            if (mType != TYPE_AUDIO && mType != TYPE_SUBTITLE) {
+                throw new IllegalStateException("Not an audio track or a subtitle track");
+            }
+            mHardOfHearing = hardOfHearing;
+            return this;
+        }
+
+        /**
+         * Sets the spoken subtitle attribute of the audio. Valid only for {@link #TYPE_AUDIO}
+         * tracks.
+         *
+         * <p>For example of broadcast, spoken subtitle information may be referred to broadcast
+         * standard (e.g. Supplementary Audio Language Descriptor of ETSI EN 300 468).
+         *
+         * @param spokenSubtitle The spoken subtitle attribute of the audio.
+         * @throws IllegalStateException if not called on an audio track
+         */
+        @NonNull
+        public Builder setSpokenSubtitle(boolean spokenSubtitle) {
+            if (mType != TYPE_AUDIO) {
+                throw new IllegalStateException("Not an audio track");
+            }
+            mSpokenSubtitle = spokenSubtitle;
+            return this;
+        }
+
+        /**
          * Sets the width of the video, in the unit of pixels. Valid only for {@link #TYPE_VIDEO}
          * tracks.
          *
          * @param videoWidth The width of the video.
          * @throws IllegalStateException if not called on a video track
          */
-        public final Builder setVideoWidth(int videoWidth) {
+        @NonNull
+        public Builder setVideoWidth(int videoWidth) {
             if (mType != TYPE_VIDEO) {
                 throw new IllegalStateException("Not a video track");
             }
@@ -495,7 +595,8 @@
          * @param videoHeight The height of the video.
          * @throws IllegalStateException if not called on a video track
          */
-        public final Builder setVideoHeight(int videoHeight) {
+        @NonNull
+        public Builder setVideoHeight(int videoHeight) {
             if (mType != TYPE_VIDEO) {
                 throw new IllegalStateException("Not a video track");
             }
@@ -510,7 +611,8 @@
          * @param videoFrameRate The frame rate of the video.
          * @throws IllegalStateException if not called on a video track
          */
-        public final Builder setVideoFrameRate(float videoFrameRate) {
+        @NonNull
+        public Builder setVideoFrameRate(float videoFrameRate) {
             if (mType != TYPE_VIDEO) {
                 throw new IllegalStateException("Not a video track");
             }
@@ -530,7 +632,8 @@
          * @param videoPixelAspectRatio The pixel aspect ratio of the video.
          * @throws IllegalStateException if not called on a video track
          */
-        public final Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) {
+        @NonNull
+        public Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) {
             if (mType != TYPE_VIDEO) {
                 throw new IllegalStateException("Not a video track");
             }
@@ -550,7 +653,8 @@
          * @param videoActiveFormatDescription The AFD code of the video.
          * @throws IllegalStateException if not called on a video track
          */
-        public final Builder setVideoActiveFormatDescription(byte videoActiveFormatDescription) {
+        @NonNull
+        public Builder setVideoActiveFormatDescription(byte videoActiveFormatDescription) {
             if (mType != TYPE_VIDEO) {
                 throw new IllegalStateException("Not a video track");
             }
@@ -563,7 +667,9 @@
          *
          * @param extra The extra information.
          */
-        public final Builder setExtra(Bundle extra) {
+        @NonNull
+        public Builder setExtra(@NonNull Bundle extra) {
+            Preconditions.checkNotNull(extra);
             mExtra = new Bundle(extra);
             return this;
         }
@@ -573,11 +679,12 @@
          *
          * @return The new {@link TvTrackInfo} instance
          */
+        @NonNull
         public TvTrackInfo build() {
             return new TvTrackInfo(mType, mId, mLanguage, mDescription, mEncrypted,
-                    mAudioChannelCount, mAudioSampleRate, mAudioDescription, mVideoWidth,
-                    mVideoHeight, mVideoFrameRate, mVideoPixelAspectRatio,
-                    mVideoActiveFormatDescription, mExtra);
+                    mAudioChannelCount, mAudioSampleRate, mAudioDescription, mHardOfHearing,
+                    mSpokenSubtitle, mVideoWidth, mVideoHeight, mVideoFrameRate,
+                    mVideoPixelAspectRatio, mVideoActiveFormatDescription, mExtra);
         }
     }
 }
diff --git a/media/java/android/media/tv/tuner/DemuxCapabilities.java b/media/java/android/media/tv/tuner/DemuxCapabilities.java
index bda166e..83abf86 100644
--- a/media/java/android/media/tv/tuner/DemuxCapabilities.java
+++ b/media/java/android/media/tv/tuner/DemuxCapabilities.java
@@ -16,11 +16,32 @@
 
 package android.media.tv.tuner;
 
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.media.tv.tuner.filter.FilterConfiguration;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Capabilities info for Demux.
+ *
  * @hide
  */
 public class DemuxCapabilities {
+
+    /** @hide */
+    @IntDef(flag = true, value = {
+            FilterConfiguration.FILTER_TYPE_TS,
+            FilterConfiguration.FILTER_TYPE_MMTP,
+            FilterConfiguration.FILTER_TYPE_IP,
+            FilterConfiguration.FILTER_TYPE_TLV,
+            FilterConfiguration.FILTER_TYPE_ALP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FilterCapabilities {}
+
     private final int mNumDemux;
     private final int mNumRecord;
     private final int mNumPlayback;
@@ -34,7 +55,8 @@
     private final int mFilterCaps;
     private final int[] mLinkCaps;
 
-    DemuxCapabilities(int numDemux, int numRecord, int numPlayback, int numTsFilter,
+    // Used by JNI
+    private DemuxCapabilities(int numDemux, int numRecord, int numPlayback, int numTsFilter,
             int numSectionFilter, int numAudioFilter, int numVideoFilter, int numPesFilter,
             int numPcrFilter, int numBytesInSectionFilter, int filterCaps, int[] linkCaps) {
         mNumDemux = numDemux;
@@ -51,52 +73,73 @@
         mLinkCaps = linkCaps;
     }
 
-    /** Gets total number of demuxes. */
+    /**
+     * Gets total number of demuxes.
+     */
     public int getNumDemux() {
         return mNumDemux;
     }
-    /** Gets max number of recordings at a time. */
+    /**
+     * Gets max number of recordings at a time.
+     */
     public int getNumRecord() {
         return mNumRecord;
     }
-    /** Gets max number of playbacks at a time. */
+    /**
+     * Gets max number of playbacks at a time.
+     */
     public int getNumPlayback() {
         return mNumPlayback;
     }
-    /** Gets number of TS filters. */
+    /**
+     * Gets number of TS filters.
+     */
     public int getNumTsFilter() {
         return mNumTsFilter;
     }
-    /** Gets number of section filters. */
+    /**
+     * Gets number of section filters.
+     */
     public int getNumSectionFilter() {
         return mNumSectionFilter;
     }
-    /** Gets number of audio filters. */
+    /**
+     * Gets number of audio filters.
+     */
     public int getNumAudioFilter() {
         return mNumAudioFilter;
     }
-    /** Gets number of video filters. */
+    /**
+     * Gets number of video filters.
+     */
     public int getNumVideoFilter() {
         return mNumVideoFilter;
     }
-    /** Gets number of PES filters. */
+    /**
+     * Gets number of PES filters.
+     */
     public int getNumPesFilter() {
         return mNumPesFilter;
     }
-    /** Gets number of PCR filters. */
+    /**
+     * Gets number of PCR filters.
+     */
     public int getNumPcrFilter() {
         return mNumPcrFilter;
     }
-    /** Gets number of bytes in the mask of a section filter. */
+    /**
+     * Gets number of bytes in the mask of a section filter.
+     */
     public int getNumBytesInSectionFilter() {
         return mNumBytesInSectionFilter;
     }
     /**
      * Gets filter capabilities in bit field.
      *
-     * The bits of the returned value is corresponding to the types in
-     * {@link TunerConstants.FilterType}.
+     * <p>The bits of the returned value is corresponding to the types in
+     * {@link FilterConfiguration}.
      */
+    @FilterCapabilities
     public int getFilterCapabilities() {
         return mFilterCaps;
     }
@@ -104,10 +147,12 @@
     /**
      * Gets link capabilities.
      *
-     * The returned array contains the same elements as the number of types in
-     * {@link TunerConstants.FilterType}.
-     * The ith element represents the filter's capability as the source for the ith type
+     * <p>The returned array contains the same elements as the number of types in
+     * {@link FilterConfiguration}.
+     * <p>The ith element represents the filter's capability as the source for the ith type.
      */
+    @Nullable
+    @Size(5)
     public int[] getLinkCapabilities() {
         return mLinkCaps;
     }
diff --git a/media/java/android/media/tv/tuner/Descrambler.java b/media/java/android/media/tv/tuner/Descrambler.java
new file mode 100644
index 0000000..0143582
--- /dev/null
+++ b/media/java/android/media/tv/tuner/Descrambler.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.media.tv.tuner.Tuner.Filter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class is used to interact with descramblers.
+ *
+ * <p> Descrambler is a hardware component used to descramble data.
+ *
+ * <p> This class controls the TIS interaction with Tuner HAL.
+ *
+ * @hide
+ */
+public class Descrambler implements AutoCloseable {
+    /** @hide */
+    @IntDef(prefix = "PID_TYPE_", value = {PID_TYPE_T, PID_TYPE_MMPT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PidType {}
+
+    /**
+     * Packet ID is used to specify packets in transport stream.
+     */
+    public static final int PID_TYPE_T = 1;
+    /**
+     * Packet ID is used to specify packets in MMTP.
+     */
+    public static final int PID_TYPE_MMPT = 2;
+
+
+    private long mNativeContext;
+
+    private native int nativeAddPid(int pidType, int pid, Filter filter);
+    private native int nativeRemovePid(int pidType, int pid, Filter filter);
+    private native int nativeSetKeyToken(byte[] keyToken);
+    private native int nativeClose();
+
+    private Descrambler() {}
+
+    /**
+     * Add packets' PID to the descrambler for descrambling.
+     *
+     * The descrambler will start descrambling packets with this PID. Multiple PIDs can be added
+     * into one descrambler instance because descambling can happen simultaneously on packets
+     * from different PIDs.
+     *
+     * If the Descrambler previously contained a filter for the PID, the old filter is replaced
+     * by the specified filter.
+     *
+     * @param pidType the type of the PID.
+     * @param pid the PID of packets to start to be descrambled.
+     * @param filter an optional filter instance to identify upper stream.
+     * @return result status of the operation.
+     */
+    public int addPid(@PidType int pidType, int pid, @Nullable Filter filter) {
+        return nativeAddPid(pidType, pid, filter);
+    }
+
+    /**
+     * Remove packets' PID from the descrambler
+     *
+     * The descrambler will stop descrambling packets with this PID.
+     *
+     * @param pidType the type of the PID.
+     * @param pid the PID of packets to stop to be descrambled.
+     * @param filter an optional filter instance to identify upper stream.
+     * @return result status of the operation.
+     */
+    public int removePid(@PidType int pidType, int pid, @Nullable Filter filter) {
+        return nativeRemovePid(pidType, pid, filter);
+    }
+
+    /**
+     * Set a key token to link descrambler to a key slot
+     *
+     * A descrambler instance can have only one key slot to link, but a key slot can hold a few
+     * keys for different purposes.
+     *
+     * @param keyToken the token to be used to link the key slot.
+     * @return result status of the operation.
+     */
+    public int setKeyToken(@Nullable byte[] keyToken) {
+        return nativeSetKeyToken(keyToken);
+    }
+
+    /**
+     * Release the descrambler instance.
+     */
+    @Override
+    public void close() {
+        nativeClose();
+    }
+
+}
diff --git a/media/java/android/media/tv/tuner/DvrSettings.java b/media/java/android/media/tv/tuner/DvrSettings.java
deleted file mode 100644
index 76160dc..0000000
--- a/media/java/android/media/tv/tuner/DvrSettings.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.tv.tuner;
-
-import android.media.tv.tuner.TunerConstants.DataFormat;
-import android.media.tv.tuner.TunerConstants.DvrSettingsType;
-
-/**
- * DVR settings.
- *
- * @hide
- */
-public class DvrSettings {
-    private int mStatusMask;
-    private int mLowThreshold;
-    private int mHighThreshold;
-    private int mPacketSize;
-
-    @DataFormat
-    private int mDataFormat;
-    @DvrSettingsType
-    private int mType;
-
-    private DvrSettings(int statusMask, int lowThreshold, int highThreshold, int packetSize,
-            @DataFormat int dataFormat, @DvrSettingsType int type) {
-        mStatusMask = statusMask;
-        mLowThreshold = lowThreshold;
-        mHighThreshold = highThreshold;
-        mPacketSize = packetSize;
-        mDataFormat = dataFormat;
-        mType = type;
-    }
-
-    /**
-     * Creates a new builder.
-     */
-    public static Builder newBuilder() {
-        return new Builder();
-    }
-
-    /**
-     * Builder for DvrSettings.
-     */
-    public static final class Builder {
-        private int mStatusMask;
-        private int mLowThreshold;
-        private int mHighThreshold;
-        private int mPacketSize;
-        @DataFormat
-        private int mDataFormat;
-        @DvrSettingsType
-        private int mType;
-
-        /**
-         * Sets status mask.
-         */
-        public Builder setStatusMask(int statusMask) {
-            this.mStatusMask = statusMask;
-            return this;
-        }
-
-        /**
-         * Sets low threshold.
-         */
-        public Builder setLowThreshold(int lowThreshold) {
-            this.mLowThreshold = lowThreshold;
-            return this;
-        }
-
-        /**
-         * Sets high threshold.
-         */
-        public Builder setHighThreshold(int highThreshold) {
-            this.mHighThreshold = highThreshold;
-            return this;
-        }
-
-        /**
-         * Sets packet size.
-         */
-        public Builder setPacketSize(int packetSize) {
-            this.mPacketSize = packetSize;
-            return this;
-        }
-
-        /**
-         * Sets data format.
-         */
-        public Builder setDataFormat(@DataFormat int dataFormat) {
-            this.mDataFormat = dataFormat;
-            return this;
-        }
-
-        /**
-         * Sets settings type.
-         */
-        public Builder setType(@DvrSettingsType int type) {
-            this.mType = type;
-            return this;
-        }
-
-        /**
-         * Builds a DvrSettings instance.
-         */
-        public DvrSettings build() {
-            return new DvrSettings(
-                    mStatusMask, mLowThreshold, mHighThreshold, mPacketSize, mDataFormat, mType);
-        }
-    }
-}
diff --git a/media/java/android/media/tv/tuner/FilterConfiguration.java b/media/java/android/media/tv/tuner/FilterConfiguration.java
deleted file mode 100644
index b80a82a..0000000
--- a/media/java/android/media/tv/tuner/FilterConfiguration.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.tv.tuner;
-
-import android.annotation.Nullable;
-import android.media.tv.tuner.TunerConstants.FilterType;
-
-import java.util.List;
-
-/**
- * Demux Filter configuration.
- *
- * @hide
- */
-public abstract class FilterConfiguration {
-    @Nullable
-    protected final Settings mSettings;
-
-    protected FilterConfiguration(Settings settings) {
-        mSettings = settings;
-    }
-
-    /**
-     * Gets filter configuration type
-     */
-    @FilterType
-    public abstract int getType();
-
-    public Settings getSettings() {
-        return mSettings;
-    }
-
-    // TODO: more builders and getters
-
-    /**
-     *  Filter configuration for a TS filter.
-     */
-    public static class TsFilterConfiguration extends FilterConfiguration {
-        private int mTpid;
-
-        private TsFilterConfiguration(Settings settings, int tpid) {
-            super(settings);
-            mTpid = tpid;
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FILTER_TYPE_TS;
-        }
-
-        /**
-         * Creates a new builder.
-         */
-        public static Builder newBuilder() {
-            return new Builder();
-        }
-
-        /**
-         * Builder for TsFilterConfiguration.
-         */
-        public static class Builder {
-            private Settings mSettings;
-            private int mTpid;
-
-            /**
-             * Sets settings.
-             */
-            public Builder setSettings(Settings settings) {
-                mSettings = settings;
-                return this;
-            }
-
-            /**
-             * Sets TPID.
-             */
-            public Builder setTpid(int tpid) {
-                mTpid = tpid;
-                return this;
-            }
-
-            /**
-             * Builds a TsFilterConfiguration instance.
-             */
-            public TsFilterConfiguration build() {
-                return new TsFilterConfiguration(mSettings, mTpid);
-            }
-        }
-    }
-
-    /**
-     *  Filter configuration for a MMTP filter.
-     */
-    public static class MmtpFilterConfiguration extends FilterConfiguration {
-        private int mMmtpPid;
-
-        public MmtpFilterConfiguration(Settings settings) {
-            super(settings);
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FILTER_TYPE_MMTP;
-        }
-    }
-
-
-    /**
-     *  Filter configuration for a IP filter.
-     */
-    public static class IpFilterConfiguration extends FilterConfiguration {
-        private byte[] mSrcIpAddress;
-        private byte[] mDstIpAddress;
-        private int mSrcPort;
-        private int mDstPort;
-        private boolean mPassthrough;
-
-        public IpFilterConfiguration(Settings settings) {
-            super(settings);
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FILTER_TYPE_IP;
-        }
-    }
-
-
-    /**
-     *  Filter configuration for a TLV filter.
-     */
-    public static class TlvFilterConfiguration extends FilterConfiguration {
-        private int mPacketType;
-        private boolean mIsCompressedIpPacket;
-        private boolean mPassthrough;
-
-        public TlvFilterConfiguration(Settings settings) {
-            super(settings);
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FILTER_TYPE_TLV;
-        }
-    }
-
-
-    /**
-     *  Filter configuration for a ALP filter.
-     */
-    public static class AlpFilterConfiguration extends FilterConfiguration {
-        private int mPacketType;
-        private int mLengthType;
-
-        public AlpFilterConfiguration(Settings settings) {
-            super(settings);
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FILTER_TYPE_ALP;
-        }
-    }
-
-
-    /**
-     *  Settings for filters of different subtypes.
-     */
-    public abstract static class Settings {
-        protected final int mType;
-
-        protected Settings(int type) {
-            mType = type;
-        }
-
-        /**
-         * Gets filter settings type.
-         * @return
-         */
-        int getType() {
-            return mType;
-        }
-    }
-
-    /**
-     *  Filter Settings for Section data according to ISO/IEC 13818-1.
-     */
-    public static class SectionSettings extends Settings {
-
-        private SectionSettings(int mainType) {
-            super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_SECTION));
-        }
-    }
-
-    /**
-     *  Bits Settings for Section Filter.
-     */
-    public static class SectionSettingsWithSectionBits extends SectionSettings {
-        private List<Byte> mFilter;
-        private List<Byte> mMask;
-        private List<Byte> mMode;
-
-        private SectionSettingsWithSectionBits(int mainType) {
-            super(mainType);
-        }
-    }
-
-    /**
-     *  Table information for Section Filter.
-     */
-    public static class SectionSettingsWithTableInfo extends SectionSettings {
-        private int mTableId;
-        private int mVersion;
-
-        private SectionSettingsWithTableInfo(int mainType) {
-            super(mainType);
-        }
-    }
-
-    /**
-     *  Filter Settings for a PES Data.
-     */
-    public static class PesSettings extends Settings {
-        private int mStreamId;
-        private boolean mIsRaw;
-
-        private PesSettings(int mainType, int streamId, boolean isRaw) {
-            super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_PES));
-            mStreamId = streamId;
-            mIsRaw = isRaw;
-        }
-
-        /**
-         * Creates a builder for PesSettings.
-         */
-        public static Builder newBuilder(int mainType) {
-            return new Builder(mainType);
-        }
-
-        /**
-         * Builder for PesSettings.
-         */
-        public static class Builder {
-            private final int mMainType;
-            private int mStreamId;
-            private boolean mIsRaw;
-
-            public Builder(int mainType) {
-                mMainType = mainType;
-            }
-
-            /**
-             * Sets stream ID.
-             */
-            public Builder setStreamId(int streamId) {
-                mStreamId = streamId;
-                return this;
-            }
-
-            /**
-             * Sets whether it's raw.
-             * true if the filter send onFilterStatus instead of onFilterEvent.
-             */
-            public Builder setIsRaw(boolean isRaw) {
-                mIsRaw = isRaw;
-                return this;
-            }
-
-            /**
-             * Builds a PesSettings instance.
-             */
-            public PesSettings build() {
-                return new PesSettings(mMainType, mStreamId, mIsRaw);
-            }
-        }
-    }
-
-    /**
-     *  Filter Settings for a Video and Audio.
-     */
-    public static class AvSettings extends Settings {
-        private boolean mIsPassthrough;
-
-        private AvSettings(int mainType, boolean isAudio) {
-            super(TunerUtils.getFilterSubtype(
-                    mainType,
-                    isAudio
-                            ? TunerConstants.FILTER_SUBTYPE_AUDIO
-                            : TunerConstants.FILTER_SUBTYPE_VIDEO));
-        }
-    }
-
-    /**
-     *  Filter Settings for a Download.
-     */
-    public static class DownloadSettings extends Settings {
-        private int mDownloadId;
-
-        public DownloadSettings(int mainType) {
-            super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_DOWNLOAD));
-        }
-    }
-
-    /**
-     *  The Settings for the record in DVR.
-     */
-    public static class RecordSettings extends Settings {
-        private int mIndexType;
-        private int mIndexMask;
-
-        public RecordSettings(int mainType) {
-            super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_RECORD));
-        }
-    }
-
-}
diff --git a/media/java/android/media/tv/tuner/FrontendCapabilities.java b/media/java/android/media/tv/tuner/FrontendCapabilities.java
deleted file mode 100644
index fcfd7c8..0000000
--- a/media/java/android/media/tv/tuner/FrontendCapabilities.java
+++ /dev/null
@@ -1,291 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.tv.tuner;
-
-/**
- * Frontend Capabilities.
- * @hide
- */
-public class FrontendCapabilities {
-    /** Analog Capabilities. */
-    public class Analog extends FrontendCapabilities {
-        private final int mTypeCap;
-        private final int mSifStandardCap;
-
-        Analog(int typeCap, int sifStandardCap) {
-            mTypeCap = typeCap;
-            mSifStandardCap = sifStandardCap;
-        }
-        /**
-         * Gets type capability.
-         */
-        public int getTypeCapability() {
-            return mTypeCap;
-        }
-        /** Gets SIF standard capability. */
-        public int getSifStandardCapability() {
-            return mSifStandardCap;
-        }
-    }
-
-    /** ATSC Capabilities. */
-    public class Atsc extends FrontendCapabilities {
-        private final int mModulationCap;
-
-        Atsc(int modulationCap) {
-            mModulationCap = modulationCap;
-        }
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-    }
-
-    /** ATSC-3 Capabilities. */
-    public class Atsc3 extends FrontendCapabilities {
-        private final int mBandwidthCap;
-        private final int mModulationCap;
-        private final int mTimeInterleaveModeCap;
-        private final int mCodeRateCap;
-        private final int mFecCap;
-        private final int mDemodOutputFormatCap;
-
-        Atsc3(int bandwidthCap, int modulationCap, int timeInterleaveModeCap, int codeRateCap,
-                int fecCap, int demodOutputFormatCap) {
-            mBandwidthCap = bandwidthCap;
-            mModulationCap = modulationCap;
-            mTimeInterleaveModeCap = timeInterleaveModeCap;
-            mCodeRateCap = codeRateCap;
-            mFecCap = fecCap;
-            mDemodOutputFormatCap = demodOutputFormatCap;
-        }
-
-        /** Gets bandwidth capability. */
-        public int getBandwidthCapability() {
-            return mBandwidthCap;
-        }
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets time interleave mod capability. */
-        public int getTimeInterleaveModeCapability() {
-            return mTimeInterleaveModeCap;
-        }
-        /** Gets code rate capability. */
-        public int getCodeRateCapability() {
-            return mCodeRateCap;
-        }
-        /** Gets FEC capability. */
-        public int getFecCapability() {
-            return mFecCap;
-        }
-        /** Gets demodulator output format capability. */
-        public int getDemodOutputFormatCapability() {
-            return mDemodOutputFormatCap;
-        }
-    }
-
-    /** DVBS Capabilities. */
-    public class Dvbs extends FrontendCapabilities {
-        private final int mModulationCap;
-        private final long mInnerFecCap;
-        private final int mStandard;
-
-        Dvbs(int modulationCap, long innerFecCap, int standard) {
-            mModulationCap = modulationCap;
-            mInnerFecCap = innerFecCap;
-            mStandard = standard;
-        }
-
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets inner FEC capability. */
-        public long getInnerFecCapability() {
-            return mInnerFecCap;
-        }
-        /** Gets DVBS standard capability. */
-        public int getStandardCapability() {
-            return mStandard;
-        }
-    }
-
-    /** DVBC Capabilities. */
-    public class Dvbc extends FrontendCapabilities {
-        private final int mModulationCap;
-        private final int mFecCap;
-        private final int mAnnexCap;
-
-        Dvbc(int modulationCap, int fecCap, int annexCap) {
-            mModulationCap = modulationCap;
-            mFecCap = fecCap;
-            mAnnexCap = annexCap;
-        }
-
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets FEC capability. */
-        public int getFecCapability() {
-            return mFecCap;
-        }
-        /** Gets annex capability. */
-        public int getAnnexCapability() {
-            return mAnnexCap;
-        }
-    }
-
-    /** DVBT Capabilities. */
-    public class Dvbt extends FrontendCapabilities {
-        private final int mTransmissionModeCap;
-        private final int mBandwidthCap;
-        private final int mConstellationCap;
-        private final int mCoderateCap;
-        private final int mHierarchyCap;
-        private final int mGuardIntervalCap;
-        private final boolean mIsT2Supported;
-        private final boolean mIsMisoSupported;
-
-        Dvbt(int transmissionModeCap, int bandwidthCap, int constellationCap, int coderateCap,
-                int hierarchyCap, int guardIntervalCap, boolean isT2Supported,
-                boolean isMisoSupported) {
-            mTransmissionModeCap = transmissionModeCap;
-            mBandwidthCap = bandwidthCap;
-            mConstellationCap = constellationCap;
-            mCoderateCap = coderateCap;
-            mHierarchyCap = hierarchyCap;
-            mGuardIntervalCap = guardIntervalCap;
-            mIsT2Supported = isT2Supported;
-            mIsMisoSupported = isMisoSupported;
-        }
-
-        /** Gets transmission mode capability. */
-        public int getTransmissionModeCapability() {
-            return mTransmissionModeCap;
-        }
-        /** Gets bandwidth capability. */
-        public int getBandwidthCapability() {
-            return mBandwidthCap;
-        }
-        /** Gets constellation capability. */
-        public int getConstellationCapability() {
-            return mConstellationCap;
-        }
-        /** Gets code rate capability. */
-        public int getCodeRateCapability() {
-            return mCoderateCap;
-        }
-        /** Gets hierarchy capability. */
-        public int getHierarchyCapability() {
-            return mHierarchyCap;
-        }
-        /** Gets guard interval capability. */
-        public int getGuardIntervalCapability() {
-            return mGuardIntervalCap;
-        }
-        /** Returns whether T2 is supported. */
-        public boolean getIsT2Supported() {
-            return mIsT2Supported;
-        }
-        /** Returns whether MISO is supported. */
-        public boolean getIsMisoSupported() {
-            return mIsMisoSupported;
-        }
-    }
-
-    /** ISDBS Capabilities. */
-    public class Isdbs extends FrontendCapabilities {
-        private final int mModulationCap;
-        private final int mCoderateCap;
-
-        Isdbs(int modulationCap, int coderateCap) {
-            mModulationCap = modulationCap;
-            mCoderateCap = coderateCap;
-        }
-
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets code rate capability. */
-        public int getCodeRateCapability() {
-            return mCoderateCap;
-        }
-    }
-
-    /** ISDBS-3 Capabilities. */
-    public class Isdbs3 extends FrontendCapabilities {
-        private final int mModulationCap;
-        private final int mCoderateCap;
-
-        Isdbs3(int modulationCap, int coderateCap) {
-            mModulationCap = modulationCap;
-            mCoderateCap = coderateCap;
-        }
-
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets code rate capability. */
-        public int getCodeRateCapability() {
-            return mCoderateCap;
-        }
-    }
-
-    /** ISDBC Capabilities. */
-    public class Isdbc extends FrontendCapabilities {
-        private final int mModeCap;
-        private final int mBandwidthCap;
-        private final int mModulationCap;
-        private final int mCoderateCap;
-        private final int mGuardIntervalCap;
-
-        Isdbc(int modeCap, int bandwidthCap, int modulationCap, int coderateCap,
-                int guardIntervalCap) {
-            mModeCap = modeCap;
-            mBandwidthCap = bandwidthCap;
-            mModulationCap = modulationCap;
-            mCoderateCap = coderateCap;
-            mGuardIntervalCap = guardIntervalCap;
-        }
-
-        /** Gets mode capability. */
-        public int getModeCapability() {
-            return mModeCap;
-        }
-        /** Gets bandwidth capability. */
-        public int getBandwidthCapability() {
-            return mBandwidthCap;
-        }
-        /** Gets modulation capability. */
-        public int getModulationCapability() {
-            return mModulationCap;
-        }
-        /** Gets code rate capability. */
-        public int getCodeRateCapability() {
-            return mCoderateCap;
-        }
-        /** Gets guard interval capability. */
-        public int getGuardIntervalCapability() {
-            return mGuardIntervalCap;
-        }
-    }
-}
diff --git a/media/java/android/media/tv/tuner/FrontendSettings.java b/media/java/android/media/tv/tuner/FrontendSettings.java
index e2e9910..ad8422c 100644
--- a/media/java/android/media/tv/tuner/FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/FrontendSettings.java
@@ -17,7 +17,6 @@
 package android.media.tv.tuner;
 
 import android.annotation.SystemApi;
-import android.media.tv.tuner.TunerConstants.FrontendSettingsType;
 
 /**
  * Frontend settings for tune and scan operations.
@@ -35,7 +34,6 @@
     /**
      * Returns the frontend type.
      */
-    @FrontendSettingsType
     public abstract int getType();
 
     /**
diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java
new file mode 100644
index 0000000..c7cc9e6d
--- /dev/null
+++ b/media/java/android/media/tv/tuner/Lnb.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.Tuner.LnbCallback;
+import android.media.tv.tuner.TunerConstants.Result;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * LNB (low-noise block downconverter) for satellite tuner.
+ *
+ * A Tuner LNB (low-noise block downconverter) is used by satellite frontend to receive the
+ * microwave signal from the satellite, amplify it, and downconvert the frequency to a lower
+ * frequency.
+ *
+ * @hide
+ */
+@SystemApi
+public class Lnb implements AutoCloseable {
+    /** @hide */
+    @IntDef({VOLTAGE_NONE, VOLTAGE_5V, VOLTAGE_11V, VOLTAGE_12V, VOLTAGE_13V, VOLTAGE_14V,
+            VOLTAGE_15V, VOLTAGE_18V, VOLTAGE_19V})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Voltage {}
+
+    /**
+     * LNB power voltage not set.
+     */
+    public static final int VOLTAGE_NONE = Constants.LnbVoltage.NONE;
+    /**
+     * LNB power voltage 5V.
+     */
+    public static final int VOLTAGE_5V = Constants.LnbVoltage.VOLTAGE_5V;
+    /**
+     * LNB power voltage 11V.
+     */
+    public static final int VOLTAGE_11V = Constants.LnbVoltage.VOLTAGE_11V;
+    /**
+     * LNB power voltage 12V.
+     */
+    public static final int VOLTAGE_12V = Constants.LnbVoltage.VOLTAGE_12V;
+    /**
+     * LNB power voltage 13V.
+     */
+    public static final int VOLTAGE_13V = Constants.LnbVoltage.VOLTAGE_13V;
+    /**
+     * LNB power voltage 14V.
+     */
+    public static final int VOLTAGE_14V = Constants.LnbVoltage.VOLTAGE_14V;
+    /**
+     * LNB power voltage 15V.
+     */
+    public static final int VOLTAGE_15V = Constants.LnbVoltage.VOLTAGE_15V;
+    /**
+     * LNB power voltage 18V.
+     */
+    public static final int VOLTAGE_18V = Constants.LnbVoltage.VOLTAGE_18V;
+    /**
+     * LNB power voltage 19V.
+     */
+    public static final int VOLTAGE_19V = Constants.LnbVoltage.VOLTAGE_19V;
+
+    /** @hide */
+    @IntDef({TONE_NONE, TONE_CONTINUOUS})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Tone {}
+
+    /**
+     * LNB tone mode not set.
+     */
+    public static final int TONE_NONE = Constants.LnbTone.NONE;
+    /**
+     * LNB continuous tone mode.
+     */
+    public static final int TONE_CONTINUOUS = Constants.LnbTone.CONTINUOUS;
+
+    /** @hide */
+    @IntDef({POSITION_UNDEFINED, POSITION_A, POSITION_B})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Position {}
+
+    /**
+     * LNB position is not defined.
+     */
+    public static final int POSITION_UNDEFINED = Constants.LnbPosition.UNDEFINED;
+    /**
+     * Position A of two-band LNBs
+     */
+    public static final int POSITION_A = Constants.LnbPosition.POSITION_A;
+    /**
+     * Position B of two-band LNBs
+     */
+    public static final int POSITION_B = Constants.LnbPosition.POSITION_B;
+
+    int mId;
+    LnbCallback mCallback;
+    Context mContext;
+
+    private native int nativeSetVoltage(int voltage);
+    private native int nativeSetTone(int tone);
+    private native int nativeSetSatellitePosition(int position);
+    private native int nativeSendDiseqcMessage(byte[] message);
+    private native int nativeClose();
+
+    Lnb(int id) {
+        mId = id;
+    }
+
+    /** @hide */
+    public void setCallback(@Nullable LnbCallback callback) {
+        mCallback = callback;
+        if (mCallback == null) {
+            return;
+        }
+    }
+
+    /**
+     * Sets the LNB's power voltage.
+     *
+     * @param voltage the power voltage constant the Lnb to use.
+     * @return result status of the operation.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Result
+    public int setVoltage(@Voltage int voltage) {
+        TunerUtils.checkTunerPermission(mContext);
+        return nativeSetVoltage(voltage);
+    }
+
+    /**
+     * Sets the LNB's tone mode.
+     *
+     * @param tone the tone mode the Lnb to use.
+     * @return result status of the operation.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Result
+    public int setTone(@Tone int tone) {
+        TunerUtils.checkTunerPermission(mContext);
+        return nativeSetTone(tone);
+    }
+
+    /**
+     * Selects the LNB's position.
+     *
+     * @param position the position the Lnb to use.
+     * @return result status of the operation.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Result
+    public int setSatellitePosition(@Position int position) {
+        TunerUtils.checkTunerPermission(mContext);
+        return nativeSetSatellitePosition(position);
+    }
+
+    /**
+     * Sends DiSEqC (Digital Satellite Equipment Control) message.
+     *
+     * The response message from the device comes back through callback onDiseqcMessage.
+     *
+     * @param message a byte array of data for DiSEqC message which is specified by EUTELSAT Bus
+     *         Functional Specification Version 4.2.
+     *
+     * @return result status of the operation.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Result
+    public int sendDiseqcMessage(@NonNull byte[] message) {
+        TunerUtils.checkTunerPermission(mContext);
+        return nativeSendDiseqcMessage(message);
+    }
+
+    /**
+     * Releases the LNB instance.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    public void close() {
+        TunerUtils.checkTunerPermission(mContext);
+        nativeClose();
+    }
+}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 43f9a89..862489f 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -21,17 +21,14 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.Context;
-import android.media.tv.tuner.FilterConfiguration.Settings;
-import android.media.tv.tuner.TunerConstants.DemuxPidType;
 import android.media.tv.tuner.TunerConstants.FilterStatus;
 import android.media.tv.tuner.TunerConstants.FilterSubtype;
-import android.media.tv.tuner.TunerConstants.FilterType;
 import android.media.tv.tuner.TunerConstants.FrontendScanType;
-import android.media.tv.tuner.TunerConstants.LnbPosition;
-import android.media.tv.tuner.TunerConstants.LnbTone;
-import android.media.tv.tuner.TunerConstants.LnbVoltage;
 import android.media.tv.tuner.TunerConstants.Result;
+import android.media.tv.tuner.dvr.Dvr;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
 import android.media.tv.tuner.filter.FilterEvent;
+import android.media.tv.tuner.filter.TimeFilter;
 import android.media.tv.tuner.frontend.FrontendCallback;
 import android.media.tv.tuner.frontend.FrontendInfo;
 import android.media.tv.tuner.frontend.FrontendStatus;
@@ -39,7 +36,6 @@
 import android.os.Looper;
 import android.os.Message;
 
-import java.io.FileDescriptor;
 import java.util.List;
 
 /**
@@ -123,6 +119,7 @@
     private native int nativeDisconnectCiCam();
     private native FrontendInfo nativeGetFrontendInfo(int id);
     private native Filter nativeOpenFilter(int type, int subType, int bufferSize);
+    private native TimeFilter nativeOpenTimeFilter();
 
     private native List<Integer> nativeGetLnbIds();
     private native Lnb nativeOpenLnbById(int id);
@@ -221,11 +218,6 @@
                     }
                     break;
                 }
-                case MSG_ON_LNB_EVENT: {
-                    if (mLnb != null && mLnb.mCallback != null) {
-                        mLnb.mCallback.onEvent(msg.arg1);
-                    }
-                }
                 default:
                     // fall through
             }
@@ -425,8 +417,14 @@
         return mFrontend.mId;
     }
 
-    /** @hide */
-    private static DemuxCapabilities getDemuxCapabilities() {
+    /**
+     * Gets Demux capabilities.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Nullable
+    public static DemuxCapabilities getDemuxCapabilities(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
         return nativeGetDemuxCapabilities();
     }
 
@@ -456,122 +454,11 @@
      * Tuner data filter.
      *
      * <p> This class is used to filter wanted data according to the filter's configuration.
+     * TODO: remove
      */
     public class Filter {
-        private long mNativeContext;
-        private FilterCallback mCallback;
-        int mId;
-
-        private native int nativeConfigureFilter(
-                int type, int subType, FilterConfiguration settings);
-        private native int nativeGetId();
-        private native int nativeSetDataSource(Filter source);
-        private native int nativeStartFilter();
-        private native int nativeStopFilter();
-        private native int nativeFlushFilter();
-        private native int nativeRead(byte[] buffer, int offset, int size);
-        private native int nativeClose();
-
-        private Filter(int id) {
-            mId = id;
-        }
-
-        private void onFilterStatus(int status) {
-            if (mHandler != null) {
-                mHandler.sendMessage(
-                        mHandler.obtainMessage(MSG_ON_FILTER_STATUS, status, 0, this));
-            }
-        }
-
-        /**
-         * Configures the filter.
-         *
-         * @param settings the settings of the filter.
-         * @return result status of the operation.
-         * @hide
-         */
-        public int configure(FilterConfiguration settings) {
-            int subType = -1;
-            Settings s = settings.getSettings();
-            if (s != null) {
-                subType = s.getType();
-            }
-            return nativeConfigureFilter(settings.getType(), subType, settings);
-        }
-
-        /**
-         * Gets the filter Id.
-         *
-         * @return the hardware resource Id for the filter.
-         * @hide
-         */
-        public int getId() {
-            return nativeGetId();
-        }
-
-        /**
-         * Sets the filter's data source.
-         *
-         * A filter uses demux as data source by default. If the data was packetized
-         * by multiple protocols, multiple filters may need to work together to
-         * extract all protocols' header. Then a filter's data source can be output
-         * from another filter.
-         *
-         * @param source the filter instance which provides data input. Switch to
-         * use demux as data source if the filter instance is NULL.
-         * @return result status of the operation.
-         * @hide
-         */
-        public int setDataSource(@Nullable Filter source) {
-            return nativeSetDataSource(source);
-        }
-
-        /**
-         * Starts the filter.
-         *
-         * @return result status of the operation.
-         * @hide
-         */
-        public int start() {
-            return nativeStartFilter();
-        }
-
-
-        /**
-         * Stops the filter.
-         *
-         * @return result status of the operation.
-         * @hide
-         */
-        public int stop() {
-            return nativeStopFilter();
-        }
-
-        /**
-         * Flushes the filter.
-         *
-         * @return result status of the operation.
-         * @hide
-         */
-        public int flush() {
-            return nativeFlushFilter();
-        }
-
-        /** @hide */
-        public int read(@NonNull byte[] buffer, int offset, int size) {
-            size = Math.min(size, buffer.length - offset);
-            return nativeRead(buffer, offset, size);
-        }
-
-        /**
-         * Release the Filter instance.
-         *
-         * @return result status of the operation.
-         * @hide
-         */
-        public int close() {
-            return nativeClose();
-        }
+        FilterCallback mCallback;
+        private Filter() {}
     }
 
     private Filter openFilter(@FilterType int mainType, @FilterSubtype int subType, int bufferSize,
@@ -587,90 +474,6 @@
         return filter;
     }
 
-    /** @hide */
-    public class Lnb {
-        private int mId;
-        private LnbCallback mCallback;
-
-        private native int nativeSetVoltage(int voltage);
-        private native int nativeSetTone(int tone);
-        private native int nativeSetSatellitePosition(int position);
-        private native int nativeSendDiseqcMessage(byte[] message);
-        private native int nativeClose();
-
-        private Lnb(int id) {
-            mId = id;
-        }
-
-        public void setCallback(@Nullable LnbCallback callback) {
-            mCallback = callback;
-            if (mCallback == null) {
-                return;
-            }
-            if (mHandler == null) {
-                mHandler = createEventHandler();
-            }
-        }
-
-        /**
-         * Sets the LNB's power voltage.
-         *
-         * @param voltage the power voltage the Lnb to use.
-         * @return result status of the operation.
-         */
-        @Result
-        public int setVoltage(@LnbVoltage int voltage) {
-            return nativeSetVoltage(voltage);
-        }
-
-        /**
-         * Sets the LNB's tone mode.
-         *
-         * @param tone the tone mode the Lnb to use.
-         * @return result status of the operation.
-         */
-        @Result
-        public int setTone(@LnbTone int tone) {
-            return nativeSetTone(tone);
-        }
-
-        /**
-         * Selects the LNB's position.
-         *
-         * @param position the position the Lnb to use.
-         * @return result status of the operation.
-         */
-        @Result
-        public int setSatellitePosition(@LnbPosition int position) {
-            return nativeSetSatellitePosition(position);
-        }
-
-        /**
-         * Sends DiSEqC (Digital Satellite Equipment Control) message.
-         *
-         * The response message from the device comes back through callback onDiseqcMessage.
-         *
-         * @param message a byte array of data for DiSEqC message which is specified by EUTELSAT Bus
-         *         Functional Specification Version 4.2.
-         *
-         * @return result status of the operation.
-         */
-        @Result
-        public int sendDiseqcMessage(byte[] message) {
-            return nativeSendDiseqcMessage(message);
-        }
-
-        /**
-         * Releases the LNB instance
-         *
-         * @return result status of the operation.
-         */
-        @Result
-        public int close() {
-            return nativeClose();
-        }
-    }
-
     private List<Integer> getLnbIds() {
         mLnbIds = nativeGetLnbIds();
         return mLnbIds;
@@ -699,81 +502,11 @@
      * <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.
+     * TODO: Remove
      */
     public class Descrambler {
-        private long mNativeContext;
-
-        private native int nativeAddPid(int pidType, int pid, Filter filter);
-        private native int nativeRemovePid(int pidType, int pid, Filter filter);
-        private native int nativeSetKeyToken(byte[] keyToken);
-        private native int nativeClose();
-
-        private Descrambler() {}
-
-        /**
-         * Add packets' PID to the descrambler for descrambling.
-         *
-         * The descrambler will start descrambling packets with this PID. Multiple PIDs can be added
-         * into one descrambler instance because descambling can happen simultaneously on packets
-         * from different PIDs.
-         *
-         * If the Descrambler previously contained a filter for the PID, the old filter is replaced
-         * by the specified filter.
-         *
-         * @param pidType the type of the PID.
-         * @param pid the PID of packets to start to be descrambled.
-         * @param filter an optional filter instance to identify upper stream.
-         * @return result status of the operation.
-         *
-         * @hide
-         */
-        public int addPid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) {
-            return nativeAddPid(pidType, pid, filter);
+        private Descrambler() {
         }
-
-        /**
-         * 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();
-        }
-
     }
 
     /**
@@ -788,137 +521,6 @@
         return nativeOpenDescrambler();
     }
 
-    // TODO: consider splitting Dvr to Playback and Recording
-    /** @hide */
-    public class Dvr {
-        private long mNativeContext;
-        private DvrCallback mCallback;
-
-        private native int nativeAttachFilter(Filter filter);
-        private native int nativeDetachFilter(Filter filter);
-        private native int nativeConfigureDvr(DvrSettings settings);
-        private native int nativeStartDvr();
-        private native int nativeStopDvr();
-        private native int nativeFlushDvr();
-        private native int nativeClose();
-        private native void nativeSetFileDescriptor(FileDescriptor fd);
-        private native int nativeRead(int size);
-        private native int nativeRead(byte[] bytes, int offset, int size);
-        private native int nativeWrite(int size);
-        private native int nativeWrite(byte[] bytes, int offset, int size);
-
-        private Dvr() {}
-
-        /**
-         * Attaches a filter to DVR interface for recording.
-         *
-         * @param filter the filter to be attached.
-         * @return result status of the operation.
-         */
-        public int attachFilter(Filter filter) {
-            return nativeAttachFilter(filter);
-        }
-
-        /**
-         * Detaches a filter from DVR interface.
-         *
-         * @param filter the filter to be detached.
-         * @return result status of the operation.
-         */
-        public int detachFilter(Filter filter) {
-            return nativeDetachFilter(filter);
-        }
-
-        /**
-         * Configures the DVR.
-         *
-         * @param settings the settings of the DVR interface.
-         * @return result status of the operation.
-         */
-        public int configure(DvrSettings settings) {
-            return nativeConfigureDvr(settings);
-        }
-
-        /**
-         * Starts DVR.
-         *
-         * Starts consuming playback data or producing data for recording.
-         *
-         * @return result status of the operation.
-         */
-        public int start() {
-            return nativeStartDvr();
-        }
-
-        /**
-         * Stops DVR.
-         *
-         * Stops consuming playback data or producing data for recording.
-         *
-         * @return result status of the operation.
-         */
-        public int stop() {
-            return nativeStopDvr();
-        }
-
-        /**
-         * Flushed DVR data.
-         *
-         * @return result status of the operation.
-         */
-        public int flush() {
-            return nativeFlushDvr();
-        }
-
-        /**
-         * closes the DVR instance to release resources.
-         *
-         * @return result status of the operation.
-         */
-        public int close() {
-            return nativeClose();
-        }
-
-        /**
-         * Sets file descriptor to read/write data.
-         */
-        public void setFileDescriptor(FileDescriptor fd) {
-            nativeSetFileDescriptor(fd);
-        }
-
-        /**
-         * Reads data from the file for DVR playback.
-         */
-        public int read(int size) {
-            return nativeRead(size);
-        }
-
-        /**
-         * Reads data from the buffer for DVR playback.
-         */
-        public int read(@NonNull byte[] bytes, int offset, int size) {
-            if (size + offset > bytes.length) {
-                throw new ArrayIndexOutOfBoundsException(
-                        "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size);
-            }
-            return nativeRead(bytes, offset, size);
-        }
-
-        /**
-         * Writes recording data to file.
-         */
-        public int write(int size) {
-            return nativeWrite(size);
-        }
-
-        /**
-         * Writes recording data to buffer.
-         */
-        public int write(@NonNull byte[] bytes, int offset, int size) {
-            return nativeWrite(bytes, offset, size);
-        }
-    }
-
     private Dvr openDvr(int type, int bufferSize) {
         Dvr dvr = nativeOpenDvr(type, bufferSize);
         return dvr;
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index d24e582..c2d6c58c 100644
--- a/media/java/android/media/tv/tuner/TunerConstants.java
+++ b/media/java/android/media/tv/tuner/TunerConstants.java
@@ -38,35 +38,6 @@
 
 
     /** @hide */
-    @IntDef({FRONTEND_TYPE_UNDEFINED, FRONTEND_TYPE_ANALOG, FRONTEND_TYPE_ATSC, FRONTEND_TYPE_ATSC3,
-            FRONTEND_TYPE_DVBC, FRONTEND_TYPE_DVBS, FRONTEND_TYPE_DVBT, FRONTEND_TYPE_ISDBS,
-            FRONTEND_TYPE_ISDBS3, FRONTEND_TYPE_ISDBT})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendType {}
-
-    /** @hide */
-    public static final int FRONTEND_TYPE_UNDEFINED = Constants.FrontendType.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ANALOG = Constants.FrontendType.ANALOG;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ATSC = Constants.FrontendType.ATSC;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ATSC3 = Constants.FrontendType.ATSC3;
-    /** @hide */
-    public static final int FRONTEND_TYPE_DVBC = Constants.FrontendType.DVBC;
-    /** @hide */
-    public static final int FRONTEND_TYPE_DVBS = Constants.FrontendType.DVBS;
-    /** @hide */
-    public static final int FRONTEND_TYPE_DVBT = Constants.FrontendType.DVBT;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ISDBS = Constants.FrontendType.ISDBS;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ISDBS3 = Constants.FrontendType.ISDBS3;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ISDBT = Constants.FrontendType.ISDBT;
-
-
-    /** @hide */
     @IntDef({FRONTEND_EVENT_TYPE_LOCKED, FRONTEND_EVENT_TYPE_NO_SIGNAL,
             FRONTEND_EVENT_TYPE_LOST_LOCK})
     @Retention(RetentionPolicy.SOURCE)
@@ -80,69 +51,6 @@
 
 
     /** @hide */
-    @IntDef({DATA_FORMAT_TS, DATA_FORMAT_PES, DATA_FORMAT_ES, DATA_FORMAT_SHV_TLV})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface DataFormat {}
-    /** @hide */
-    public static final int DATA_FORMAT_TS = Constants.DataFormat.TS;
-    /** @hide */
-    public static final int DATA_FORMAT_PES = Constants.DataFormat.PES;
-    /** @hide */
-    public static final int DATA_FORMAT_ES = Constants.DataFormat.ES;
-    /** @hide */
-    public static final int DATA_FORMAT_SHV_TLV = Constants.DataFormat.SHV_TLV;
-
-
-    /** @hide */
-    @IntDef({DEMUX_T_PID, DEMUX_MMPT_PID})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface DemuxPidType {}
-    /** @hide */
-    public static final int DEMUX_T_PID = 1;
-    /** @hide */
-    public static final int DEMUX_MMPT_PID = 2;
-
-    /** @hide */
-    @IntDef({FRONTEND_SETTINGS_ANALOG, FRONTEND_SETTINGS_ATSC, FRONTEND_SETTINGS_ATSC3,
-            FRONTEND_SETTINGS_DVBS, FRONTEND_SETTINGS_DVBC, FRONTEND_SETTINGS_DVBT,
-            FRONTEND_SETTINGS_ISDBS, FRONTEND_SETTINGS_ISDBS3, FRONTEND_SETTINGS_ISDBT})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendSettingsType {}
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ANALOG = 1;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ATSC = 2;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ATSC3 = 3;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_DVBS = 4;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_DVBC = 5;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_DVBT = 6;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ISDBS = 7;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ISDBS3 = 8;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ISDBT = 9;
-
-    /** @hide */
-    @IntDef({FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FilterType {}
-    /** @hide */
-    public static final int FILTER_TYPE_TS = Constants.DemuxFilterMainType.TS;
-    /** @hide */
-    public static final int FILTER_TYPE_MMTP = Constants.DemuxFilterMainType.MMTP;
-    /** @hide */
-    public static final int FILTER_TYPE_IP = Constants.DemuxFilterMainType.IP;
-    /** @hide */
-    public static final int FILTER_TYPE_TLV = Constants.DemuxFilterMainType.TLV;
-    /** @hide */
-    public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP;
-
-    /** @hide */
     @IntDef({FILTER_SUBTYPE_UNDEFINED, FILTER_SUBTYPE_SECTION, FILTER_SUBTYPE_PES,
             FILTER_SUBTYPE_AUDIO, FILTER_SUBTYPE_VIDEO, FILTER_SUBTYPE_DOWNLOAD,
             FILTER_SUBTYPE_RECORD, FILTER_SUBTYPE_TS, FILTER_SUBTYPE_PCR, FILTER_SUBTYPE_TEMI,
@@ -186,8 +94,8 @@
     public static final int FILTER_SUBTYPE_PTP = 16;
 
     /** @hide */
-    @IntDef({FILTER_STATUS_DATA_READY, FILTER_STATUS_LOW_WATER, FILTER_STATUS_HIGH_WATER,
-            FILTER_STATUS_OVERFLOW})
+    @IntDef(flag = true, prefix = "FILTER_STATUS_", value = {FILTER_STATUS_DATA_READY,
+            FILTER_STATUS_LOW_WATER, FILTER_STATUS_HIGH_WATER, FILTER_STATUS_OVERFLOW})
     @Retention(RetentionPolicy.SOURCE)
     public @interface FilterStatus {}
 
@@ -216,6 +124,187 @@
      */
     public static final int FILTER_STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW;
 
+    /**
+     * Indexes can be tagged through TS (Transport Stream) header.
+     *
+     * @hide
+     */
+    @IntDef(flag = true, value = {TS_INDEX_FIRST_PACKET, TS_INDEX_PAYLOAD_UNIT_START_INDICATOR,
+            TS_INDEX_CHANGE_TO_NOT_SCRAMBLED, TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED,
+            TS_INDEX_CHANGE_TO_ODD_SCRAMBLED, TS_INDEX_DISCONTINUITY_INDICATOR,
+            TS_INDEX_RANDOM_ACCESS_INDICATOR, TS_INDEX_PRIORITY_INDICATOR, TS_INDEX_PCR_FLAG,
+            TS_INDEX_OPCR_FLAG, TS_INDEX_SPLICING_POINT_FLAG, TS_INDEX_PRIVATE_DATA,
+            TS_INDEX_ADAPTATION_EXTENSION_FLAG})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TsIndex {}
+
+    /**
+     * TS index FIRST_PACKET.
+     * @hide
+     */
+    public static final int TS_INDEX_FIRST_PACKET = Constants.DemuxTsIndex.FIRST_PACKET;
+    /**
+     * TS index PAYLOAD_UNIT_START_INDICATOR.
+     * @hide
+     */
+    public static final int TS_INDEX_PAYLOAD_UNIT_START_INDICATOR =
+            Constants.DemuxTsIndex.PAYLOAD_UNIT_START_INDICATOR;
+    /**
+     * TS index CHANGE_TO_NOT_SCRAMBLED.
+     * @hide
+     */
+    public static final int TS_INDEX_CHANGE_TO_NOT_SCRAMBLED =
+            Constants.DemuxTsIndex.CHANGE_TO_NOT_SCRAMBLED;
+    /**
+     * TS index CHANGE_TO_EVEN_SCRAMBLED.
+     * @hide
+     */
+    public static final int TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED =
+            Constants.DemuxTsIndex.CHANGE_TO_EVEN_SCRAMBLED;
+    /**
+     * TS index CHANGE_TO_ODD_SCRAMBLED.
+     * @hide
+     */
+    public static final int TS_INDEX_CHANGE_TO_ODD_SCRAMBLED =
+            Constants.DemuxTsIndex.CHANGE_TO_ODD_SCRAMBLED;
+    /**
+     * TS index DISCONTINUITY_INDICATOR.
+     * @hide
+     */
+    public static final int TS_INDEX_DISCONTINUITY_INDICATOR =
+            Constants.DemuxTsIndex.DISCONTINUITY_INDICATOR;
+    /**
+     * TS index RANDOM_ACCESS_INDICATOR.
+     * @hide
+     */
+    public static final int TS_INDEX_RANDOM_ACCESS_INDICATOR =
+            Constants.DemuxTsIndex.RANDOM_ACCESS_INDICATOR;
+    /**
+     * TS index PRIORITY_INDICATOR.
+     * @hide
+     */
+    public static final int TS_INDEX_PRIORITY_INDICATOR = Constants.DemuxTsIndex.PRIORITY_INDICATOR;
+    /**
+     * TS index PCR_FLAG.
+     * @hide
+     */
+    public static final int TS_INDEX_PCR_FLAG = Constants.DemuxTsIndex.PCR_FLAG;
+    /**
+     * TS index OPCR_FLAG.
+     * @hide
+     */
+    public static final int TS_INDEX_OPCR_FLAG = Constants.DemuxTsIndex.OPCR_FLAG;
+    /**
+     * TS index SPLICING_POINT_FLAG.
+     * @hide
+     */
+    public static final int TS_INDEX_SPLICING_POINT_FLAG =
+            Constants.DemuxTsIndex.SPLICING_POINT_FLAG;
+    /**
+     * TS index PRIVATE_DATA.
+     * @hide
+     */
+    public static final int TS_INDEX_PRIVATE_DATA = Constants.DemuxTsIndex.PRIVATE_DATA;
+    /**
+     * TS index ADAPTATION_EXTENSION_FLAG.
+     * @hide
+     */
+    public static final int TS_INDEX_ADAPTATION_EXTENSION_FLAG =
+            Constants.DemuxTsIndex.ADAPTATION_EXTENSION_FLAG;
+
+    /**
+     * Indexes can be tagged by Start Code in PES (Packetized Elementary Stream)
+     * according to ISO/IEC 13818-1.
+     * @hide
+     */
+    @IntDef(flag = true, value = {SC_INDEX_I_FRAME, SC_INDEX_P_FRAME, SC_INDEX_B_FRAME,
+            SC_INDEX_SEQUENCE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScIndex {}
+
+    /**
+     * SC index for a new I-frame.
+     * @hide
+     */
+    public static final int SC_INDEX_I_FRAME = Constants.DemuxScIndex.I_FRAME;
+    /**
+     * SC index for a new P-frame.
+     * @hide
+     */
+    public static final int SC_INDEX_P_FRAME = Constants.DemuxScIndex.P_FRAME;
+    /**
+     * SC index for a new B-frame.
+     * @hide
+     */
+    public static final int SC_INDEX_B_FRAME = Constants.DemuxScIndex.B_FRAME;
+    /**
+     * SC index for a new sequence.
+     * @hide
+     */
+    public static final int SC_INDEX_SEQUENCE = Constants.DemuxScIndex.SEQUENCE;
+
+
+    /**
+     * Indexes can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2.
+     *
+     * @hide
+     */
+    @IntDef(flag = true,
+            value = {SC_HEVC_INDEX_SPS, SC_HEVC_INDEX_AUD, SC_HEVC_INDEX_SLICE_CE_BLA_W_LP,
+            SC_HEVC_INDEX_SLICE_BLA_W_RADL, SC_HEVC_INDEX_SLICE_BLA_N_LP,
+            SC_HEVC_INDEX_SLICE_IDR_W_RADL, SC_HEVC_INDEX_SLICE_IDR_N_LP,
+            SC_HEVC_INDEX_SLICE_TRAIL_CRA})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScHevcIndex {}
+
+    /**
+     * SC HEVC index SPS.
+     * @hide
+     */
+    public static final int SC_HEVC_INDEX_SPS = Constants.DemuxScHevcIndex.SPS;
+    /**
+     * SC HEVC index AUD.
+     * @hide
+     */
+    public static final int SC_HEVC_INDEX_AUD = Constants.DemuxScHevcIndex.AUD;
+    /**
+     * SC HEVC index SLICE_CE_BLA_W_LP.
+     * @hide
+     */
+    public static final int SC_HEVC_INDEX_SLICE_CE_BLA_W_LP =
+            Constants.DemuxScHevcIndex.SLICE_CE_BLA_W_LP;
+    /**
+     * SC HEVC index SLICE_BLA_W_RADL.
+     * @hide
+     */
+    public static final int SC_HEVC_INDEX_SLICE_BLA_W_RADL =
+            Constants.DemuxScHevcIndex.SLICE_BLA_W_RADL;
+    /**
+     * SC HEVC index SLICE_BLA_N_LP.
+     * @hide
+     */
+    public static final int SC_HEVC_INDEX_SLICE_BLA_N_LP =
+            Constants.DemuxScHevcIndex.SLICE_BLA_N_LP;
+    /**
+     * SC HEVC index SLICE_IDR_W_RADL.
+     * @hide
+     */
+    public static final int SC_HEVC_INDEX_SLICE_IDR_W_RADL =
+            Constants.DemuxScHevcIndex.SLICE_IDR_W_RADL;
+    /**
+     * SC HEVC index SLICE_IDR_N_LP.
+     * @hide
+     */
+    public static final int SC_HEVC_INDEX_SLICE_IDR_N_LP =
+            Constants.DemuxScHevcIndex.SLICE_IDR_N_LP;
+    /**
+     * SC HEVC index SLICE_TRAIL_CRA.
+     * @hide
+     */
+    public static final int SC_HEVC_INDEX_SLICE_TRAIL_CRA =
+            Constants.DemuxScHevcIndex.SLICE_TRAIL_CRA;
+
+
     /** @hide */
     @IntDef({FRONTEND_SCAN_UNDEFINED, FRONTEND_SCAN_AUTO, FRONTEND_SCAN_BLIND})
     @Retention(RetentionPolicy.SOURCE)
@@ -227,41 +316,6 @@
     /** @hide */
     public static final int FRONTEND_SCAN_BLIND = Constants.FrontendScanType.SCAN_BLIND;
 
-    /** @hide */
-    @IntDef({SCAN_MESSAGE_TYPE_LOCKED, SCAN_MESSAGE_TYPE_END, SCAN_MESSAGE_TYPE_PROGRESS_PERCENT,
-            SCAN_MESSAGE_TYPE_FREQUENCY, SCAN_MESSAGE_TYPE_SYMBOL_RATE, SCAN_MESSAGE_TYPE_PLP_IDS,
-            SCAN_MESSAGE_TYPE_GROUP_IDS, SCAN_MESSAGE_TYPE_INPUT_STREAM_IDS,
-            SCAN_MESSAGE_TYPE_STANDARD, SCAN_MESSAGE_TYPE_ATSC3_PLP_INFO})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ScanMessageType {}
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_LOCKED = Constants.FrontendScanMessageType.LOCKED;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_END = Constants.FrontendScanMessageType.END;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_PROGRESS_PERCENT =
-            Constants.FrontendScanMessageType.PROGRESS_PERCENT;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_FREQUENCY =
-            Constants.FrontendScanMessageType.FREQUENCY;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_SYMBOL_RATE =
-            Constants.FrontendScanMessageType.SYMBOL_RATE;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_PLP_IDS = Constants.FrontendScanMessageType.PLP_IDS;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_GROUP_IDS =
-            Constants.FrontendScanMessageType.GROUP_IDS;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_INPUT_STREAM_IDS =
-            Constants.FrontendScanMessageType.INPUT_STREAM_IDS;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_STANDARD =
-            Constants.FrontendScanMessageType.STANDARD;
-    /** @hide */
-    public static final int SCAN_MESSAGE_TYPE_ATSC3_PLP_INFO =
-            Constants.FrontendScanMessageType.ATSC3_PLP_INFO;
-
 
     /** @hide */
     @IntDef({FRONTEND_STATUS_TYPE_DEMOD_LOCK, FRONTEND_STATUS_TYPE_SNR, FRONTEND_STATUS_TYPE_BER,
@@ -777,73 +831,6 @@
             Constants.FrontendDvbtHierarchy.HIERARCHY_4_INDEPTH;
 
     /** @hide */
-    @IntDef({FRONTEND_ANALOG_TYPE_UNDEFINED, FRONTEND_ANALOG_TYPE_PAL, FRONTEND_ANALOG_TYPE_SECAM,
-            FRONTEND_ANALOG_TYPE_NTSC})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendAnalogType {}
-    /** @hide */
-    public static final int FRONTEND_ANALOG_TYPE_UNDEFINED = Constants.FrontendAnalogType.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_TYPE_PAL = Constants.FrontendAnalogType.PAL;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_TYPE_SECAM = Constants.FrontendAnalogType.SECAM;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_TYPE_NTSC = Constants.FrontendAnalogType.NTSC;
-
-    /** @hide */
-    @IntDef({FRONTEND_ANALOG_SIF_UNDEFINED, FRONTEND_ANALOG_SIF_BG, FRONTEND_ANALOG_SIF_BG_A2,
-            FRONTEND_ANALOG_SIF_BG_NICAM, FRONTEND_ANALOG_SIF_I, FRONTEND_ANALOG_SIF_DK,
-            FRONTEND_ANALOG_SIF_DK1, FRONTEND_ANALOG_SIF_DK2, FRONTEND_ANALOG_SIF_DK3,
-            FRONTEND_ANALOG_SIF_DK_NICAM, FRONTEND_ANALOG_SIF_L, FRONTEND_ANALOG_SIF_M,
-            FRONTEND_ANALOG_SIF_M_BTSC, FRONTEND_ANALOG_SIF_M_A2, FRONTEND_ANALOG_SIF_M_EIA_J,
-            FRONTEND_ANALOG_SIF_I_NICAM, FRONTEND_ANALOG_SIF_L_NICAM, FRONTEND_ANALOG_SIF_L_PRIME})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendAnalogSifStandard {}
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_UNDEFINED =
-            Constants.FrontendAnalogSifStandard.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_BG = Constants.FrontendAnalogSifStandard.BG;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_BG_A2 = Constants.FrontendAnalogSifStandard.BG_A2;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_BG_NICAM =
-            Constants.FrontendAnalogSifStandard.BG_NICAM;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_I = Constants.FrontendAnalogSifStandard.I;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_DK = Constants.FrontendAnalogSifStandard.DK;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_DK1 = Constants.FrontendAnalogSifStandard.DK1;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_DK2 = Constants.FrontendAnalogSifStandard.DK2;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_DK3 = Constants.FrontendAnalogSifStandard.DK3;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_DK_NICAM =
-            Constants.FrontendAnalogSifStandard.DK_NICAM;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_L = Constants.FrontendAnalogSifStandard.L;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_M = Constants.FrontendAnalogSifStandard.M;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_M_BTSC = Constants.FrontendAnalogSifStandard.M_BTSC;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_M_A2 = Constants.FrontendAnalogSifStandard.M_A2;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_M_EIA_J =
-            Constants.FrontendAnalogSifStandard.M_EIA_J;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_I_NICAM =
-            Constants.FrontendAnalogSifStandard.I_NICAM;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_L_NICAM =
-            Constants.FrontendAnalogSifStandard.L_NICAM;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_L_PRIME =
-            Constants.FrontendAnalogSifStandard.L_PRIME;
-
-    /** @hide */
     @IntDef({FRONTEND_ATSC_MODULATION_UNDEFINED, FRONTEND_ATSC_MODULATION_AUTO,
             FRONTEND_ATSC_MODULATION_MOD_8VSB, FRONTEND_ATSC_MODULATION_MOD_16VSB})
     @Retention(RetentionPolicy.SOURCE)
@@ -1302,79 +1289,40 @@
     /** @hide */
     public static final int FILTER_SETTINGS_ALP = Constants.DemuxFilterMainType.ALP;
 
-    /** @hide */
-    @IntDef({DVR_SETTINGS_RECORD, DVR_SETTINGS_PLAYBACK})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface DvrSettingsType {}
-    /** @hide */
-    public static final int DVR_SETTINGS_RECORD = Constants.DvrType.RECORD;
-    /** @hide */
-    public static final int DVR_SETTINGS_PLAYBACK = Constants.DvrType.PLAYBACK;
-
-
-    /** @hide */
-    @IntDef({LNB_VOLTAGE_NONE, LNB_VOLTAGE_5V, LNB_VOLTAGE_11V, LNB_VOLTAGE_12V, LNB_VOLTAGE_13V,
-            LNB_VOLTAGE_14V, LNB_VOLTAGE_15V, LNB_VOLTAGE_18V, LNB_VOLTAGE_19V})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface LnbVoltage {}
-    /** @hide */
-    public static final int LNB_VOLTAGE_NONE = Constants.LnbVoltage.NONE;
-    /** @hide */
-    public static final int LNB_VOLTAGE_5V = Constants.LnbVoltage.VOLTAGE_5V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_11V = Constants.LnbVoltage.VOLTAGE_11V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_12V = Constants.LnbVoltage.VOLTAGE_12V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_13V = Constants.LnbVoltage.VOLTAGE_13V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_14V = Constants.LnbVoltage.VOLTAGE_14V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_15V = Constants.LnbVoltage.VOLTAGE_15V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_18V = Constants.LnbVoltage.VOLTAGE_18V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_19V = Constants.LnbVoltage.VOLTAGE_19V;
-
-    /** @hide */
-    @IntDef({LNB_TONE_NONE, LNB_TONE_CONTINUOUS})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface LnbTone {}
-    /** @hide */
-    public static final int LNB_TONE_NONE = Constants.LnbTone.NONE;
-    /** @hide */
-    public static final int LNB_TONE_CONTINUOUS = Constants.LnbTone.CONTINUOUS;
-
-    /** @hide */
-    @IntDef({LNB_POSITION_UNDEFINED, LNB_POSITION_A, LNB_POSITION_B})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface LnbPosition {}
-    /** @hide */
-    public static final int LNB_POSITION_UNDEFINED = Constants.LnbPosition.UNDEFINED;
-    /** @hide */
-    public static final int LNB_POSITION_A = Constants.LnbPosition.POSITION_A;
-    /** @hide */
-    public static final int LNB_POSITION_B = Constants.LnbPosition.POSITION_B;
-
 
     /** @hide */
     @IntDef({RESULT_SUCCESS, RESULT_UNAVAILABLE, RESULT_NOT_INITIALIZED, RESULT_INVALID_STATE,
             RESULT_INVALID_ARGUMENT, RESULT_OUT_OF_MEMORY, RESULT_UNKNOWN_ERROR})
     @Retention(RetentionPolicy.SOURCE)
     public @interface Result {}
-    /** @hide */
+
+    /**
+     * Operation succeeded.
+     */
     public static final int RESULT_SUCCESS = Constants.Result.SUCCESS;
-    /** @hide */
+    /**
+     * Operation failed because the corresponding resources are not available.
+     */
     public static final int RESULT_UNAVAILABLE = Constants.Result.UNAVAILABLE;
-    /** @hide */
+    /**
+     * Operation failed because the corresponding resources are not initialized.
+     */
     public static final int RESULT_NOT_INITIALIZED = Constants.Result.NOT_INITIALIZED;
-    /** @hide */
+    /**
+     * Operation failed because it's not in a valid state.
+     */
     public static final int RESULT_INVALID_STATE = Constants.Result.INVALID_STATE;
-    /** @hide */
+    /**
+     * Operation failed because there are invalid arguments.
+     */
     public static final int RESULT_INVALID_ARGUMENT = Constants.Result.INVALID_ARGUMENT;
-    /** @hide */
+    /**
+     * Memory allocation failed.
+     */
     public static final int RESULT_OUT_OF_MEMORY = Constants.Result.OUT_OF_MEMORY;
-    /** @hide */
+    /**
+     * Operation failed due to unknown errors.
+     */
     public static final int RESULT_UNKNOWN_ERROR = Constants.Result.UNKNOWN_ERROR;
 
     private TunerConstants() {
diff --git a/media/java/android/media/tv/tuner/TunerUtils.java b/media/java/android/media/tv/tuner/TunerUtils.java
index a7ccb02..8780b72 100644
--- a/media/java/android/media/tv/tuner/TunerUtils.java
+++ b/media/java/android/media/tv/tuner/TunerUtils.java
@@ -20,7 +20,8 @@
 import android.content.pm.PackageManager;
 import android.hardware.tv.tuner.V1_0.Constants;
 import android.media.tv.tuner.TunerConstants.FilterSubtype;
-import android.media.tv.tuner.TunerConstants.FilterType;
+import android.media.tv.tuner.filter.FilterConfiguration;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
 
 /**
  * Utility class for tuner framework.
@@ -50,7 +51,7 @@
      * @param subtype filter subtype.
      */
     public static int getFilterSubtype(@FilterType int mainType, @FilterSubtype int subtype) {
-        if (mainType == TunerConstants.FILTER_TYPE_TS) {
+        if (mainType == FilterConfiguration.FILTER_TYPE_TS) {
             switch (subtype) {
                 case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
                     return Constants.DemuxTsFilterType.UNDEFINED;
@@ -73,7 +74,7 @@
                 default:
                     break;
             }
-        } else if (mainType == TunerConstants.FILTER_TYPE_MMTP) {
+        } else if (mainType == FilterConfiguration.FILTER_TYPE_MMTP) {
             switch (subtype) {
                 case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
                     return Constants.DemuxMmtpFilterType.UNDEFINED;
@@ -95,7 +96,7 @@
                     break;
             }
 
-        } else if (mainType == TunerConstants.FILTER_TYPE_IP) {
+        } else if (mainType == FilterConfiguration.FILTER_TYPE_IP) {
             switch (subtype) {
                 case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
                     return Constants.DemuxIpFilterType.UNDEFINED;
@@ -112,7 +113,7 @@
                 default:
                     break;
             }
-        } else if (mainType == TunerConstants.FILTER_TYPE_TLV) {
+        } else if (mainType == FilterConfiguration.FILTER_TYPE_TLV) {
             switch (subtype) {
                 case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
                     return Constants.DemuxTlvFilterType.UNDEFINED;
@@ -125,7 +126,7 @@
                 default:
                     break;
             }
-        } else if (mainType == TunerConstants.FILTER_TYPE_ALP) {
+        } else if (mainType == FilterConfiguration.FILTER_TYPE_ALP) {
             switch (subtype) {
                 case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
                     return Constants.DemuxAlpFilterType.UNDEFINED;
diff --git a/media/java/android/media/tv/tuner/dvr/Dvr.java b/media/java/android/media/tv/tuner/dvr/Dvr.java
new file mode 100644
index 0000000..f90042b
--- /dev/null
+++ b/media/java/android/media/tv/tuner/dvr/Dvr.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.dvr;
+
+import android.annotation.BytesLong;
+import android.annotation.NonNull;
+import android.media.tv.tuner.Tuner.DvrCallback;
+import android.media.tv.tuner.Tuner.Filter;
+import android.media.tv.tuner.TunerConstants.Result;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Digital Video Record (DVR) interface provides record control on Demux's output buffer and
+ * playback control on Demux's input buffer.
+ *
+ * @hide
+ */
+public class Dvr {
+    private long mNativeContext;
+    private DvrCallback mCallback;
+
+    private native int nativeAttachFilter(Filter filter);
+    private native int nativeDetachFilter(Filter filter);
+    private native int nativeConfigureDvr(DvrSettings settings);
+    private native int nativeStartDvr();
+    private native int nativeStopDvr();
+    private native int nativeFlushDvr();
+    private native int nativeClose();
+    private native void nativeSetFileDescriptor(int fd);
+    private native int nativeRead(long size);
+    private native int nativeRead(byte[] bytes, long offset, long size);
+    private native int nativeWrite(long size);
+    private native int nativeWrite(byte[] bytes, long offset, long size);
+
+    private Dvr() {}
+
+    /**
+     * Attaches a filter to DVR interface for recording.
+     *
+     * @param filter the filter to be attached.
+     * @return result status of the operation.
+     */
+    @Result
+    public int attachFilter(@NonNull Filter filter) {
+        return nativeAttachFilter(filter);
+    }
+
+    /**
+     * Detaches a filter from DVR interface.
+     *
+     * @param filter the filter to be detached.
+     * @return result status of the operation.
+     */
+    @Result
+    public int detachFilter(@NonNull Filter filter) {
+        return nativeDetachFilter(filter);
+    }
+
+    /**
+     * Configures the DVR.
+     *
+     * @param settings the settings of the DVR interface.
+     * @return result status of the operation.
+     */
+    @Result
+    public int configure(@NonNull DvrSettings settings) {
+        return nativeConfigureDvr(settings);
+    }
+
+    /**
+     * Starts DVR.
+     *
+     * <p>Starts consuming playback data or producing data for recording.
+     *
+     * @return result status of the operation.
+     */
+    @Result
+    public int start() {
+        return nativeStartDvr();
+    }
+
+    /**
+     * Stops DVR.
+     *
+     * <p>Stops consuming playback data or producing data for recording.
+     *
+     * @return result status of the operation.
+     */
+    @Result
+    public int stop() {
+        return nativeStopDvr();
+    }
+
+    /**
+     * Flushed DVR data.
+     *
+     * <p>The data in DVR buffer is cleared.
+     *
+     * @return result status of the operation.
+     */
+    @Result
+    public int flush() {
+        return nativeFlushDvr();
+    }
+
+    /**
+     * Closes the DVR instance to release resources.
+     *
+     * @return result status of the operation.
+     */
+    @Result
+    public int close() {
+        return nativeClose();
+    }
+
+    /**
+     * Sets file descriptor to read/write data.
+     *
+     * @param fd the file descriptor to read/write data.
+     */
+    public void setFileDescriptor(@NonNull ParcelFileDescriptor fd) {
+        nativeSetFileDescriptor(fd.getFd());
+    }
+
+    /**
+     * Reads data from the file for DVR playback.
+     *
+     * @param size the maximum number of bytes to read.
+     * @return the number of bytes read.
+     */
+    public int read(@BytesLong long size) {
+        return nativeRead(size);
+    }
+
+    /**
+     * Reads data from the buffer for DVR playback and copies to the given byte array.
+     *
+     * @param bytes the byte array to store the data.
+     * @param offset the index of the first byte in {@code bytes} to copy to.
+     * @param size the maximum number of bytes to read.
+     * @return the number of bytes read.
+     */
+    public int read(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) {
+        if (size + offset > bytes.length) {
+            throw new ArrayIndexOutOfBoundsException(
+                    "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size);
+        }
+        return nativeRead(bytes, offset, size);
+    }
+
+    /**
+     * Writes recording data to file.
+     *
+     * @param size the maximum number of bytes to write.
+     * @return the number of bytes written.
+     */
+    public int write(@BytesLong long size) {
+        return nativeWrite(size);
+    }
+
+    /**
+     * Writes recording data to buffer.
+     *
+     * @param bytes the byte array stores the data to be written to DVR.
+     * @param offset the index of the first byte in {@code bytes} to be written to DVR.
+     * @param size the maximum number of bytes to write.
+     * @return the number of bytes written.
+     */
+    public int write(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) {
+        return nativeWrite(bytes, offset, size);
+    }
+}
diff --git a/media/java/android/media/tv/tuner/dvr/DvrSettings.java b/media/java/android/media/tv/tuner/dvr/DvrSettings.java
new file mode 100644
index 0000000..46efd7a
--- /dev/null
+++ b/media/java/android/media/tv/tuner/dvr/DvrSettings.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.dvr;
+
+import android.annotation.BytesLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerConstants.FilterStatus;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * DVR settings used to configure {@link Dvr}.
+ *
+ * @hide
+ */
+public class DvrSettings {
+
+    /** @hide */
+    @IntDef(prefix = "DATA_FORMAT_",
+            value = {DATA_FORMAT_TS, DATA_FORMAT_PES, DATA_FORMAT_ES, DATA_FORMAT_SHV_TLV})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DataFormat {}
+
+    /**
+     * Transport Stream.
+     */
+    public static final int DATA_FORMAT_TS = Constants.DataFormat.TS;
+    /**
+     * Packetized Elementary Stream.
+     */
+    public static final int DATA_FORMAT_PES = Constants.DataFormat.PES;
+    /**
+     * Elementary Stream.
+     */
+    public static final int DATA_FORMAT_ES = Constants.DataFormat.ES;
+    /**
+     * TLV (type-length-value) Stream for SHV
+     */
+    public static final int DATA_FORMAT_SHV_TLV = Constants.DataFormat.SHV_TLV;
+
+
+    /** @hide */
+    @IntDef(prefix = "TYPE_", value = {TYPE_RECORD, TYPE_PLAYBACK})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
+    /**
+     * DVR for recording.
+     */
+    public static final int TYPE_RECORD = Constants.DvrType.RECORD;
+    /**
+     * DVR for playback of recorded programs.
+     */
+    public static final int TYPE_PLAYBACK = Constants.DvrType.PLAYBACK;
+
+
+
+    private final int mStatusMask;
+    private final long mLowThreshold;
+    private final long mHighThreshold;
+    private final long mPacketSize;
+
+    @DataFormat
+    private final int mDataFormat;
+    @Type
+    private final int mType;
+
+    private DvrSettings(int statusMask, long lowThreshold, long highThreshold, long packetSize,
+            @DataFormat int dataFormat, @Type int type) {
+        mStatusMask = statusMask;
+        mLowThreshold = lowThreshold;
+        mHighThreshold = highThreshold;
+        mPacketSize = packetSize;
+        mDataFormat = dataFormat;
+        mType = type;
+    }
+
+    /**
+     * Creates a builder for {@link DvrSettings}.
+     */
+    @NonNull
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link DvrSettings}.
+     */
+    public static final class Builder {
+        private int mStatusMask;
+        private long mLowThreshold;
+        private long mHighThreshold;
+        private long mPacketSize;
+        @DataFormat
+        private int mDataFormat;
+        @Type
+        private int mType;
+
+        /**
+         * Sets status mask.
+         */
+        @NonNull
+        public Builder setStatusMask(@FilterStatus int statusMask) {
+            this.mStatusMask = statusMask;
+            return this;
+        }
+
+        /**
+         * Sets low threshold in bytes.
+         */
+        @NonNull
+        public Builder setLowThreshold(@BytesLong long lowThreshold) {
+            this.mLowThreshold = lowThreshold;
+            return this;
+        }
+
+        /**
+         * Sets high threshold in bytes.
+         */
+        @NonNull
+        public Builder setHighThreshold(@BytesLong long highThreshold) {
+            this.mHighThreshold = highThreshold;
+            return this;
+        }
+
+        /**
+         * Sets packet size in bytes.
+         */
+        @NonNull
+        public Builder setPacketSize(@BytesLong long packetSize) {
+            this.mPacketSize = packetSize;
+            return this;
+        }
+
+        /**
+         * Sets data format.
+         */
+        @NonNull
+        public Builder setDataFormat(@DataFormat int dataFormat) {
+            this.mDataFormat = dataFormat;
+            return this;
+        }
+
+        /**
+         * Sets settings type.
+         */
+        @NonNull
+        public Builder setType(@Type int type) {
+            this.mType = type;
+            return this;
+        }
+
+        /**
+         * Builds a {@link DvrSettings} object.
+         */
+        @NonNull
+        public DvrSettings build() {
+            return new DvrSettings(
+                    mStatusMask, mLowThreshold, mHighThreshold, mPacketSize, mDataFormat, mType);
+        }
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
similarity index 65%
copy from media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
copy to media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
index 306de84..f0fe533 100644
--- a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
+++ b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
@@ -17,15 +17,19 @@
 package android.media.tv.tuner.filter;
 
 /**
- * Extra Meta Data from AD (Audio Descriptor) according to
- * ETSI TS 101 154 V2.1.1.
+ * Filter configuration for a ALP filter.
  * @hide
  */
-public class AudioExtraMetaData {
-    private byte mAdFade;
-    private byte mAdPan;
-    private byte mVersionTextTag;
-    private byte mAdGainCenter;
-    private byte mAdGainFront;
-    private byte mAdGainSurround;
+public class AlpFilterConfiguration extends FilterConfiguration {
+    private int mPacketType;
+    private int mLengthType;
+
+    public AlpFilterConfiguration(Settings settings) {
+        super(settings);
+    }
+
+    @Override
+    public int getType() {
+        return FilterConfiguration.FILTER_TYPE_ALP;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/AudioDescriptor.java b/media/java/android/media/tv/tuner/filter/AudioDescriptor.java
new file mode 100644
index 0000000..c88c07f
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/AudioDescriptor.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.filter;
+
+/**
+ * Meta data from AD (Audio Descriptor) according to ETSI TS 101 154 V2.1.1.
+ *
+ * @hide
+ */
+public class AudioDescriptor {
+    private final byte mAdFade;
+    private final byte mAdPan;
+    private final char mVersionTextTag;
+    private final byte mAdGainCenter;
+    private final byte mAdGainFront;
+    private final byte mAdGainSurround;
+
+    // This constructor is used by JNI code only
+    private AudioDescriptor(byte adFade, byte adPan, char versionTextTag, byte adGainCenter,
+            byte adGainFront, byte adGainSurround) {
+        mAdFade = adFade;
+        mAdPan = adPan;
+        mVersionTextTag = versionTextTag;
+        mAdGainCenter = adGainCenter;
+        mAdGainFront = adGainFront;
+        mAdGainSurround = adGainSurround;
+    }
+
+    /**
+     * Gets AD fade byte.
+     *
+     * <p>Takes values between 0x00 (representing no fade of the main programme sound) and 0xFF
+     * (representing a full fade). Over the range 0x00 to 0xFE one lsb represents a step in
+     * attenuation of the programme sound of 0.3 dB giving a range of 76.2 dB. The fade value of
+     * 0xFF represents no programme sound at all (i.e. mute).
+     */
+    public byte getAdFade() {
+        return mAdFade;
+    }
+
+    /**
+     * Gets AD pan byte.
+     *
+     * <p>Takes values between 0x00 representing a central forward presentation of the audio
+     * description and 0xFF, each increment representing a 360/256 degree step clockwise looking
+     * down on the listener (i.e. just over 1.4 degrees).
+     */
+    public byte getAdPan() {
+        return mAdPan;
+    }
+
+    /**
+     * Gets AD version tag. A single ASCII character version indicates the version.
+     *
+     * <p>A single ASCII character version designator (here "1" indicates revision 1).
+     */
+    public char getVersionTextTag() {
+        return mVersionTextTag;
+    }
+
+    /**
+     * Gets AD gain byte center in dB.
+     *
+     * <p>Represents a signed value in dB. Takes values between 0x7F (representing +76.2 dB boost of
+     * the main programme center) and 0x80 (representing a full fade). Over the range 0x00 to 0x7F
+     * one lsb represents a step in boost of the programme center of 0.6 dB giving a maximum boost
+     * of +76.2 dB. Over the range 0x81 to 0x00 one lsb represents a step in attenuation of the
+     * programme center of 0.6 dB giving a maximum attenuation of -76.2 dB. The gain value of 0x80
+     * represents no main center level at all (i.e. mute).
+     */
+    public byte getAdGainCenter() {
+        return mAdGainCenter;
+    }
+
+    /**
+     * Gets AD gain byte front in dB.
+     *
+     * <p>Same as {@link #getAdGainCenter()}, but applied to left and right front channel.
+     */
+    public byte getAdGainFront() {
+        return mAdGainFront;
+    }
+
+    /**
+     * Gets AD gain byte surround in dB.
+     *
+     * <p>Same as {@link #getAdGainCenter()}, but applied to all surround channels
+     */
+    public byte getAdGainSurround() {
+        return mAdGainSurround;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/AvSettings.java b/media/java/android/media/tv/tuner/filter/AvSettings.java
new file mode 100644
index 0000000..a7c49d5
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/AvSettings.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.filter;
+
+import android.media.tv.tuner.TunerConstants;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * Filter Settings for a Video and Audio.
+ * @hide
+ */
+public class AvSettings extends Settings {
+    private boolean mIsPassthrough;
+
+    private AvSettings(int mainType, boolean isAudio) {
+        super(TunerUtils.getFilterSubtype(
+                mainType,
+                isAudio
+                        ? TunerConstants.FILTER_SUBTYPE_AUDIO
+                        : TunerConstants.FILTER_SUBTYPE_VIDEO));
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/DownloadEvent.java b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
index 548fa77..591e4e5 100644
--- a/media/java/android/media/tv/tuner/filter/DownloadEvent.java
+++ b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
@@ -16,15 +16,65 @@
 
 package android.media.tv.tuner.filter;
 
+import android.media.tv.tuner.Tuner.Filter;
+
 /**
- * Download event.
+ * Filter event sent from {@link Filter} objects with download type.
+ *
  * @hide
  */
 public class DownloadEvent extends FilterEvent {
-    private int mItemId;
-    private int mMpuSequenceNumber;
-    private int mItemFragmentIndex;
-    private int mLastItemFragmentIndex;
-    private int mDataLength;
+    private final int mItemId;
+    private final int mMpuSequenceNumber;
+    private final int mItemFragmentIndex;
+    private final int mLastItemFragmentIndex;
+    private final int mDataLength;
+
+    // This constructor is used by JNI code only
+    private DownloadEvent(int itemId, int mpuSequenceNumber, int itemFragmentIndex,
+            int lastItemFragmentIndex, int dataLength) {
+        mItemId = itemId;
+        mMpuSequenceNumber = mpuSequenceNumber;
+        mItemFragmentIndex = itemFragmentIndex;
+        mLastItemFragmentIndex = lastItemFragmentIndex;
+        mDataLength = dataLength;
+    }
+
+    /**
+     * Gets item ID.
+     */
+    public int getItemId() {
+        return mItemId;
+    }
+
+    /**
+     * Gets MPU sequence number of filtered data.
+     */
+    public int getMpuSequenceNumber() {
+        return mMpuSequenceNumber;
+    }
+
+    /**
+     * Gets current index of the current item.
+     *
+     * An item can be stored in different fragments.
+     */
+    public int getItemFragmentIndex() {
+        return mItemFragmentIndex;
+    }
+
+    /**
+     * Gets last index of the current item.
+     */
+    public int getLastItemFragmentIndex() {
+        return mLastItemFragmentIndex;
+    }
+
+    /**
+     * Gets data size in bytes of filtered data.
+     */
+    public int getDataLength() {
+        return mDataLength;
+    }
 }
 
diff --git a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
similarity index 66%
copy from media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
copy to media/java/android/media/tv/tuner/filter/DownloadSettings.java
index 306de84..0742b11 100644
--- a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
+++ b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
@@ -16,16 +16,17 @@
 
 package android.media.tv.tuner.filter;
 
+import android.media.tv.tuner.TunerConstants;
+import android.media.tv.tuner.TunerUtils;
+
 /**
- * Extra Meta Data from AD (Audio Descriptor) according to
- * ETSI TS 101 154 V2.1.1.
+ * Filter Settings for a Download.
  * @hide
  */
-public class AudioExtraMetaData {
-    private byte mAdFade;
-    private byte mAdPan;
-    private byte mVersionTextTag;
-    private byte mAdGainCenter;
-    private byte mAdGainFront;
-    private byte mAdGainSurround;
+public class DownloadSettings extends Settings {
+    private int mDownloadId;
+
+    public DownloadSettings(int mainType) {
+        super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_DOWNLOAD));
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
new file mode 100644
index 0000000..804c0c5
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.filter;
+
+import android.annotation.BytesLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.tv.tuner.Tuner.FilterCallback;
+
+/**
+ * Tuner data filter.
+ *
+ * <p>This class is used to filter wanted data according to the filter's configuration.
+ *
+ * @hide
+ */
+public class Filter implements AutoCloseable {
+    private long mNativeContext;
+    private FilterCallback mCallback;
+    private final int mId;
+
+    private native int nativeConfigureFilter(
+            int type, int subType, FilterConfiguration settings);
+    private native int nativeGetId();
+    private native int nativeSetDataSource(Filter source);
+    private native int nativeStartFilter();
+    private native int nativeStopFilter();
+    private native int nativeFlushFilter();
+    private native int nativeRead(byte[] buffer, long offset, long size);
+    private native int nativeClose();
+
+    private Filter(int id) {
+        mId = id;
+    }
+
+    private void onFilterStatus(int status) {
+    }
+
+    /**
+     * Configures the filter.
+     *
+     * @param config the configuration of the filter.
+     * @return result status of the operation.
+     */
+    public int configure(@NonNull FilterConfiguration config) {
+        int subType = -1;
+        Settings s = config.getSettings();
+        if (s != null) {
+            subType = s.getType();
+        }
+        return nativeConfigureFilter(config.getType(), subType, config);
+    }
+
+    /**
+     * Gets the filter Id.
+     */
+    public int getId() {
+        return nativeGetId();
+    }
+
+    /**
+     * Sets the filter's data source.
+     *
+     * A filter uses demux as data source by default. If the data was packetized
+     * by multiple protocols, multiple filters may need to work together to
+     * extract all protocols' header. Then a filter's data source can be output
+     * from another filter.
+     *
+     * @param source the filter instance which provides data input. Switch to
+     * use demux as data source if the filter instance is NULL.
+     * @return result status of the operation.
+     */
+    public int setDataSource(@Nullable Filter source) {
+        return nativeSetDataSource(source);
+    }
+
+    /**
+     * Starts filtering data.
+     *
+     * @return result status of the operation.
+     */
+    public int start() {
+        return nativeStartFilter();
+    }
+
+
+    /**
+     * Stops filtering data.
+     *
+     * @return result status of the operation.
+     */
+    public int stop() {
+        return nativeStopFilter();
+    }
+
+    /**
+     * Flushes the filter. Data in filter buffer is cleared.
+     *
+     * @return result status of the operation.
+     */
+    public int flush() {
+        return nativeFlushFilter();
+    }
+
+    /**
+     * Copies filtered data from filter buffer to the given byte array.
+     *
+     * @param buffer the buffer to store the filtered data.
+     * @param offset the index of the first byte in {@code buffer} to write.
+     * @param size the maximum number of bytes to read.
+     * @return the number of bytes read.
+     */
+    public int read(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) {
+        size = Math.min(size, buffer.length - offset);
+        return nativeRead(buffer, offset, size);
+    }
+
+    /**
+     * Releases the Filter instance.
+     */
+    @Override
+    public void close() {
+        nativeClose();
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
new file mode 100644
index 0000000..99b10cd
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.filter;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.hardware.tv.tuner.V1_0.Constants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Filter configuration used to configure filters.
+ *
+ * @hide
+ */
+public abstract class FilterConfiguration {
+
+    /** @hide */
+    @IntDef({FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FilterType {}
+
+    /**
+     * TS filter type.
+     */
+    public static final int FILTER_TYPE_TS = Constants.DemuxFilterMainType.TS;
+    /**
+     * MMTP filter type.
+     */
+    public static final int FILTER_TYPE_MMTP = Constants.DemuxFilterMainType.MMTP;
+    /**
+     * IP filter type.
+     */
+    public static final int FILTER_TYPE_IP = Constants.DemuxFilterMainType.IP;
+    /**
+     * TLV filter type.
+     */
+    public static final int FILTER_TYPE_TLV = Constants.DemuxFilterMainType.TLV;
+    /**
+     * ALP filter type.
+     */
+    public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP;
+
+    @Nullable
+    private final Settings mSettings;
+
+    /* package */ FilterConfiguration(Settings settings) {
+        mSettings = settings;
+    }
+
+    /**
+     * Gets filter configuration type.
+     * @hide
+     */
+    @FilterType
+    public abstract int getType();
+
+    /** @hide */
+    @Nullable
+    public Settings getSettings() {
+        return mSettings;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
similarity index 60%
copy from media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
copy to media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
index 306de84..c896368 100644
--- a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
+++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
@@ -17,15 +17,22 @@
 package android.media.tv.tuner.filter;
 
 /**
- * Extra Meta Data from AD (Audio Descriptor) according to
- * ETSI TS 101 154 V2.1.1.
+ * Filter configuration for a IP filter.
  * @hide
  */
-public class AudioExtraMetaData {
-    private byte mAdFade;
-    private byte mAdPan;
-    private byte mVersionTextTag;
-    private byte mAdGainCenter;
-    private byte mAdGainFront;
-    private byte mAdGainSurround;
+public class IpFilterConfiguration extends FilterConfiguration {
+    private byte[] mSrcIpAddress;
+    private byte[] mDstIpAddress;
+    private int mSrcPort;
+    private int mDstPort;
+    private boolean mPassthrough;
+
+    public IpFilterConfiguration(Settings settings) {
+        super(settings);
+    }
+
+    @Override
+    public int getType() {
+        return FilterConfiguration.FILTER_TYPE_IP;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java b/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java
index 4da1d21..09489ed 100644
--- a/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java
+++ b/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java
@@ -16,10 +16,25 @@
 
 package android.media.tv.tuner.filter;
 
+import android.media.tv.tuner.Tuner.Filter;
+
 /**
- * IP payload event.
+ * Filter event sent from {@link Filter} objects with IP payload type.
+ *
  * @hide
  */
 public class IpPayloadEvent extends FilterEvent {
-    private int mDataLength;
+    private final int mDataLength;
+
+    // This constructor is used by JNI code only
+    private IpPayloadEvent(int dataLength) {
+        mDataLength = dataLength;
+    }
+
+    /**
+     * Gets data size in bytes of filtered data.
+     */
+    public int getDataLength() {
+        return mDataLength;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java
index 7703248..37f94ae 100644
--- a/media/java/android/media/tv/tuner/filter/MediaEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java
@@ -16,20 +16,111 @@
 
 package android.media.tv.tuner.filter;
 
-import android.os.NativeHandle;
+import android.annotation.Nullable;
+import android.media.tv.tuner.Tuner.Filter;
 
 /**
- * Media event.
+ * Filter event sent from {@link Filter} objects with media type.
+ *
  * @hide
  */
-public class MediaEvent extends FilterEvent {
-    private int mStreamId;
-    private boolean mIsPtsPresent;
-    private long mPts;
-    private int mDataLength;
-    private NativeHandle mHandle;
-    private boolean mIsSecureMemory;
-    private int mMpuSequenceNumber;
-    private boolean mIsPrivateData;
-    private AudioExtraMetaData mExtraMetaData;
+public class MediaEvent extends FilterEvent{
+    private final int mStreamId;
+    private final boolean mIsPtsPresent;
+    private final long mPts;
+    private final int mDataLength;
+    private final Object mLinearBuffer;
+    private final boolean mIsSecureMemory;
+    private final int mMpuSequenceNumber;
+    private final boolean mIsPrivateData;
+    private final AudioDescriptor mExtraMetaData;
+
+    // This constructor is used by JNI code only
+    private MediaEvent(int streamId, boolean isPtsPresent, long pts, int dataLength, Object buffer,
+            boolean isSecureMemory, int mpuSequenceNumber, boolean isPrivateData,
+            AudioDescriptor extraMetaData) {
+        mStreamId = streamId;
+        mIsPtsPresent = isPtsPresent;
+        mPts = pts;
+        mDataLength = dataLength;
+        mLinearBuffer = buffer;
+        mIsSecureMemory = isSecureMemory;
+        mMpuSequenceNumber = mpuSequenceNumber;
+        mIsPrivateData = isPrivateData;
+        mExtraMetaData = extraMetaData;
+    }
+
+    /**
+     * Gets stream ID.
+     */
+    public int getStreamId() {
+        return mStreamId;
+    }
+
+    /**
+     * Returns whether PTS is present.
+     *
+     * @return {@code true} if PTS is present in PES header; {@code false} otherwise.
+     */
+    public boolean getIsPtsPresent() {
+        return mIsPtsPresent;
+    }
+
+    /**
+     * Gets PTS (Presentation Time Stamp) for audio or video frame.
+     */
+    public long getPts() {
+        return mPts;
+    }
+
+    /**
+     * Gets data size in bytes of audio or video frame.
+     */
+    public int getDataLength() {
+        return mDataLength;
+    }
+
+    /**
+     * Gets a linear buffer associated to the memory where audio or video data stays.
+     * TODO: use LinearBuffer when it's ready.
+     *
+     * @hide
+     */
+    public Object getLinearBuffer() {
+        return mLinearBuffer;
+    }
+
+    /**
+     * Returns whether the data is secure.
+     *
+     * @return {@code true} if the data is in secure area, and isn't mappable;
+     *         {@code false} otherwise.
+     */
+    public boolean getIsSecureMemory() {
+        return mIsSecureMemory;
+    }
+
+    /**
+     * Gets MPU sequence number of filtered data.
+     */
+    public int getMpuSequenceNumber() {
+        return mMpuSequenceNumber;
+    }
+
+    /**
+     * Returns whether the data is private.
+     *
+     * @return {@code true} if the data is in private; {@code false} otherwise.
+     */
+    public boolean getIsPrivateData() {
+        return mIsPrivateData;
+    }
+
+    /**
+     * Gets audio extra metadata.
+     */
+    @Nullable
+    public AudioDescriptor getExtraMetaData() {
+        return mExtraMetaData;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
similarity index 67%
copy from media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
copy to media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
index 306de84..9045ce6 100644
--- a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
+++ b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
@@ -17,15 +17,18 @@
 package android.media.tv.tuner.filter;
 
 /**
- * Extra Meta Data from AD (Audio Descriptor) according to
- * ETSI TS 101 154 V2.1.1.
+ * Filter configuration for a MMTP filter.
  * @hide
  */
-public class AudioExtraMetaData {
-    private byte mAdFade;
-    private byte mAdPan;
-    private byte mVersionTextTag;
-    private byte mAdGainCenter;
-    private byte mAdGainFront;
-    private byte mAdGainSurround;
+public class MmtpFilterConfiguration extends FilterConfiguration {
+    private int mMmtpPid;
+
+    public MmtpFilterConfiguration(Settings settings) {
+        super(settings);
+    }
+
+    @Override
+    public int getType() {
+        return FilterConfiguration.FILTER_TYPE_MMTP;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
index dbd8c77..7f37994 100644
--- a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
@@ -16,11 +16,34 @@
 
 package android.media.tv.tuner.filter;
 
+import android.media.tv.tuner.Tuner.Filter;
+
 /**
- * MMPT record event.
+ * Filter event sent from {@link Filter} objects with MMTP type.
+ *
  * @hide
  */
 public class MmtpRecordEvent extends FilterEvent {
-    private int mScHevcIndexMask;
-    private long mByteNumber;
+    private final int mScHevcIndexMask;
+    private final long mByteNumber;
+
+    // This constructor is used by JNI code only
+    private MmtpRecordEvent(int scHevcIndexMask, long byteNumber) {
+        mScHevcIndexMask = scHevcIndexMask;
+        mByteNumber = byteNumber;
+    }
+
+    /**
+     * Gets indexes which can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2.
+     */
+    public int getScHevcIndexMask() {
+        return mScHevcIndexMask;
+    }
+
+    /**
+     * Gets the byte number from beginning of the filter's output.
+     */
+    public long getByteNumber() {
+        return mByteNumber;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/PesEvent.java b/media/java/android/media/tv/tuner/filter/PesEvent.java
index 16536e2..60251bf 100644
--- a/media/java/android/media/tv/tuner/filter/PesEvent.java
+++ b/media/java/android/media/tv/tuner/filter/PesEvent.java
@@ -16,12 +16,43 @@
 
 package android.media.tv.tuner.filter;
 
+import android.media.tv.tuner.Tuner.Filter;
+
 /**
- * PES event.
+ * Filter event sent from {@link Filter} objects with PES type.
+ *
  * @hide
  */
 public class PesEvent extends FilterEvent {
-    private int mStreamId;
-    private int mDataLength;
-    private int mMpuSequenceNumber;
+    private final int mStreamId;
+    private final int mDataLength;
+    private final int mMpuSequenceNumber;
+
+    // This constructor is used by JNI code only
+    private PesEvent(int streamId, int dataLength, int mpuSequenceNumber) {
+        mStreamId = streamId;
+        mDataLength = dataLength;
+        mMpuSequenceNumber = mpuSequenceNumber;
+    }
+
+    /**
+     * Gets stream ID.
+     */
+    public int getStreamId() {
+        return mStreamId;
+    }
+
+    /**
+     * Gets data size in bytes of filtered data.
+     */
+    public int getDataLength() {
+        return mDataLength;
+    }
+
+    /**
+     * Gets MPU sequence number of filtered data.
+     */
+    public int getMpuSequenceNumber() {
+        return mMpuSequenceNumber;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/PesSettings.java b/media/java/android/media/tv/tuner/filter/PesSettings.java
new file mode 100644
index 0000000..f38abf1
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/PesSettings.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.media.tv.tuner.TunerConstants;
+import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
+
+/**
+ * Filter Settings for a PES Data.
+ *
+ * @hide
+ */
+public class PesSettings extends Settings {
+    private final int mStreamId;
+    private final boolean mIsRaw;
+
+    private PesSettings(@FilterType int mainType, int streamId, boolean isRaw) {
+        super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_PES));
+        mStreamId = streamId;
+        mIsRaw = isRaw;
+    }
+
+    /**
+     * Creates a builder for {@link PesSettings}.
+     *
+     * @param mainType the filter main type of the settings.
+     */
+    @NonNull
+    public static Builder newBuilder(@FilterType int mainType) {
+        return new Builder(mainType);
+    }
+
+    /**
+     * Builder for {@link PesSettings}.
+     */
+    public static class Builder {
+        private final int mMainType;
+        private int mStreamId;
+        private boolean mIsRaw;
+
+        private Builder(int mainType) {
+            mMainType = mainType;
+        }
+
+        /**
+         * Sets stream ID.
+         *
+         * @param streamId the stream ID.
+         */
+        @NonNull
+        public Builder setStreamId(int streamId) {
+            mStreamId = streamId;
+            return this;
+        }
+
+        /**
+         * Sets whether it's raw.
+         *
+         * @param isRaw {@code true} if the data is raw. Filter sends onFilterStatus callback
+         * instead of onFilterEvent for raw data. {@code false} otherwise.
+         */
+        @NonNull
+        public Builder setIsRaw(boolean isRaw) {
+            mIsRaw = isRaw;
+            return this;
+        }
+
+        /**
+         * Builds a {@link PesSettings} object.
+         */
+        @NonNull
+        public PesSettings build() {
+            return new PesSettings(mMainType, mStreamId, mIsRaw);
+        }
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java
similarity index 64%
copy from media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
copy to media/java/android/media/tv/tuner/filter/RecordSettings.java
index 306de84..701868a 100644
--- a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
+++ b/media/java/android/media/tv/tuner/filter/RecordSettings.java
@@ -16,16 +16,18 @@
 
 package android.media.tv.tuner.filter;
 
+import android.media.tv.tuner.TunerConstants;
+import android.media.tv.tuner.TunerUtils;
+
 /**
- * Extra Meta Data from AD (Audio Descriptor) according to
- * ETSI TS 101 154 V2.1.1.
+ * The Settings for the record in DVR.
  * @hide
  */
-public class AudioExtraMetaData {
-    private byte mAdFade;
-    private byte mAdPan;
-    private byte mVersionTextTag;
-    private byte mAdGainCenter;
-    private byte mAdGainFront;
-    private byte mAdGainSurround;
+public class RecordSettings extends Settings {
+    private int mIndexType;
+    private int mIndexMask;
+
+    public RecordSettings(int mainType) {
+        super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_RECORD));
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java b/media/java/android/media/tv/tuner/filter/SectionSettings.java
similarity index 66%
copy from media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
copy to media/java/android/media/tv/tuner/filter/SectionSettings.java
index 306de84..36e3d7c 100644
--- a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
+++ b/media/java/android/media/tv/tuner/filter/SectionSettings.java
@@ -16,16 +16,16 @@
 
 package android.media.tv.tuner.filter;
 
+import android.media.tv.tuner.TunerConstants;
+import android.media.tv.tuner.TunerUtils;
+
 /**
- * Extra Meta Data from AD (Audio Descriptor) according to
- * ETSI TS 101 154 V2.1.1.
+ * Filter Settings for Section data according to ISO/IEC 13818-1.
  * @hide
  */
-public class AudioExtraMetaData {
-    private byte mAdFade;
-    private byte mAdPan;
-    private byte mVersionTextTag;
-    private byte mAdGainCenter;
-    private byte mAdGainFront;
-    private byte mAdGainSurround;
+public class SectionSettings extends Settings {
+
+    SectionSettings(int mainType) {
+        super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_SECTION));
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java
similarity index 68%
copy from media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
copy to media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java
index 306de84..414ea67 100644
--- a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
+++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java
@@ -16,16 +16,18 @@
 
 package android.media.tv.tuner.filter;
 
+import java.util.List;
+
 /**
- * Extra Meta Data from AD (Audio Descriptor) according to
- * ETSI TS 101 154 V2.1.1.
+ * Bits Settings for Section Filter.
  * @hide
  */
-public class AudioExtraMetaData {
-    private byte mAdFade;
-    private byte mAdPan;
-    private byte mVersionTextTag;
-    private byte mAdGainCenter;
-    private byte mAdGainFront;
-    private byte mAdGainSurround;
+public class SectionSettingsWithSectionBits extends SectionSettings {
+    private List<Byte> mFilter;
+    private List<Byte> mMask;
+    private List<Byte> mMode;
+
+    private SectionSettingsWithSectionBits(int mainType) {
+        super(mainType);
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
similarity index 68%
copy from media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
copy to media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
index 306de84..0df1d73 100644
--- a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
+++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
@@ -17,15 +17,14 @@
 package android.media.tv.tuner.filter;
 
 /**
- * Extra Meta Data from AD (Audio Descriptor) according to
- * ETSI TS 101 154 V2.1.1.
+ * Table information for Section Filter.
  * @hide
  */
-public class AudioExtraMetaData {
-    private byte mAdFade;
-    private byte mAdPan;
-    private byte mVersionTextTag;
-    private byte mAdGainCenter;
-    private byte mAdGainFront;
-    private byte mAdGainSurround;
+public class SectionSettingsWithTableInfo extends SectionSettings {
+    private int mTableId;
+    private int mVersion;
+
+    private SectionSettingsWithTableInfo(int mainType) {
+        super(mainType);
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java b/media/java/android/media/tv/tuner/filter/Settings.java
similarity index 68%
rename from media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
rename to media/java/android/media/tv/tuner/filter/Settings.java
index 306de84..146aca7 100644
--- a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
+++ b/media/java/android/media/tv/tuner/filter/Settings.java
@@ -17,15 +17,23 @@
 package android.media.tv.tuner.filter;
 
 /**
- * Extra Meta Data from AD (Audio Descriptor) according to
- * ETSI TS 101 154 V2.1.1.
+ * Settings for filters of different subtypes.
+ *
  * @hide
  */
-public class AudioExtraMetaData {
-    private byte mAdFade;
-    private byte mAdPan;
-    private byte mVersionTextTag;
-    private byte mAdGainCenter;
-    private byte mAdGainFront;
-    private byte mAdGainSurround;
+public abstract class Settings {
+    private final int mType;
+
+    /* package */ Settings(int type) {
+        mType = type;
+    }
+
+    /**
+     * Gets filter settings type.
+     *
+     * @hide
+     */
+    public int getType() {
+        return mType;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/TemiEvent.java b/media/java/android/media/tv/tuner/filter/TemiEvent.java
index 3841604..031fa5c 100644
--- a/media/java/android/media/tv/tuner/filter/TemiEvent.java
+++ b/media/java/android/media/tv/tuner/filter/TemiEvent.java
@@ -16,12 +16,46 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.NonNull;
+import android.media.tv.tuner.Tuner.Filter;
+
 /**
- * TEMI event.
+ * Filter event sent from {@link Filter} objects for Timed External Media Information (TEMI) data.
+ *
  * @hide
  */
 public class TemiEvent extends FilterEvent {
-    private long mPts;
-    private byte mDescrTag;
-    private byte[] mDescrData;
+    private final long mPts;
+    private final byte mDescrTag;
+    private final byte[] mDescrData;
+
+    // This constructor is used by JNI code only
+    private TemiEvent(long pts, byte descrTag, byte[] descrData) {
+        mPts = pts;
+        mDescrTag = descrTag;
+        mDescrData = descrData;
+    }
+
+
+    /**
+     * Gets PTS (Presentation Time Stamp) for audio or video frame.
+     */
+    public long getPts() {
+        return mPts;
+    }
+
+    /**
+     * Gets TEMI descriptor tag.
+     */
+    public byte getDescriptorTag() {
+        return mDescrTag;
+    }
+
+    /**
+     * Gets TEMI descriptor.
+     */
+    @NonNull
+    public byte[] getDescriptorData() {
+        return mDescrData;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/TimeFilter.java b/media/java/android/media/tv/tuner/filter/TimeFilter.java
new file mode 100644
index 0000000..c975004
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/TimeFilter.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.filter;
+
+import android.annotation.Nullable;
+import android.media.tv.tuner.TunerConstants.Result;
+
+/**
+ *  A timer filter is used to filter data based on timestamps.
+ *
+ *  <p> If the timestamp is set, data is discarded if its timestamp is smaller than the
+ *  timestamp in this time filter.
+ *
+ *  <p> The format of the timestamps is the same as PTS defined in ISO/IEC 13818-1:2019. The
+ *  timestamps may or may not be related to PTS or DTS.
+ *
+ * @hide
+ */
+public class TimeFilter implements AutoCloseable {
+    private native int nativeSetTimestamp(long timestamp);
+    private native int nativeClearTimestamp();
+    private native Long nativeGetTimestamp();
+    private native Long nativeGetSourceTime();
+    private native int nativeClose();
+
+    private boolean mEnable = false;
+
+    /**
+     * Set timestamp for time based filter.
+     *
+     * It is used to set initial timestamp and enable time filtering. Once set, the time will be
+     * increased automatically like a clock. Contents are discarded if their timestamps are
+     * older than the time in the time filter.
+     *
+     * This method can be called more than once to reset the initial timestamp.
+     *
+     * @param timestamp initial timestamp for the time filter before it's increased. It's
+     * based on the 90KHz counter, and it's the same format as PTS (Presentation Time Stamp)
+     * defined in ISO/IEC 13818-1:2019. The timestamps may or may not be related to PTS or DTS.
+     * @return result status of the operation.
+     */
+    @Result
+    public int setCurrentTimestamp(long timestamp) {
+        int res = nativeSetTimestamp(timestamp);
+        // TODO: use a constant for SUCCESS
+        if (res == 0) {
+            mEnable = true;
+        }
+        return res;
+    }
+
+    /**
+     * Clear the timestamp in the time filter.
+     *
+     * It is used to clear the time value of the time filter. Time filtering is disabled then.
+     *
+     * @return result status of the operation.
+     */
+    @Result
+    public int clearTimestamp() {
+        int res = nativeClearTimestamp();
+        if (res == 0) {
+            mEnable = false;
+        }
+        return res;
+    }
+
+    /**
+     * Get the current time in the time filter.
+     *
+     * It is used to inquiry current time in the time filter.
+     *
+     * @return current timestamp in the time filter. It's based on the 90KHz counter, and it's
+     * the same format as PTS (Presentation Time Stamp) defined in ISO/IEC 13818-1:2019. The
+     * timestamps may or may not be related to PTS or DTS. {@code null} if the timestamp is
+     * never set.
+     */
+    @Nullable
+    public Long getTimeStamp() {
+        if (!mEnable) {
+            return null;
+        }
+        return nativeGetTimestamp();
+    }
+
+    /**
+     * Get the timestamp from the beginning of incoming data stream.
+     *
+     * It is used to inquiry the timestamp from the beginning of incoming data stream.
+     *
+     * @return first timestamp of incoming data stream. It's based on the 90KHz counter, and
+     * it's the same format as PTS (Presentation Time Stamp) defined in ISO/IEC 13818-1:2019.
+     * The timestamps may or may not be related to PTS or DTS.
+     */
+    @Nullable
+    public Long getSourceTime() {
+        if (!mEnable) {
+            return null;
+        }
+        return nativeGetSourceTime();
+    }
+
+    /**
+     * Close the Time Filter instance
+     *
+     * It is to release the TimeFilter instance. Resources are reclaimed so the instance must
+     * not be accessed after this method is called.
+     */
+    @Override
+    public void close() {
+        nativeClose();
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
similarity index 62%
copy from media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
copy to media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
index 306de84..de8ee75 100644
--- a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
+++ b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
@@ -17,15 +17,20 @@
 package android.media.tv.tuner.filter;
 
 /**
- * Extra Meta Data from AD (Audio Descriptor) according to
- * ETSI TS 101 154 V2.1.1.
+ * Filter configuration for a TLV filter.
  * @hide
  */
-public class AudioExtraMetaData {
-    private byte mAdFade;
-    private byte mAdPan;
-    private byte mVersionTextTag;
-    private byte mAdGainCenter;
-    private byte mAdGainFront;
-    private byte mAdGainSurround;
+public class TlvFilterConfiguration extends FilterConfiguration {
+    private int mPacketType;
+    private boolean mIsCompressedIpPacket;
+    private boolean mPassthrough;
+
+    public TlvFilterConfiguration(Settings settings) {
+        super(settings);
+    }
+
+    @Override
+    public int getType() {
+        return FilterConfiguration.FILTER_TYPE_TLV;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java
new file mode 100644
index 0000000..d0241b6
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+
+/**
+ * Filter configuration for a TS filter.
+ *
+ * @hide
+ */
+public class TsFilterConfiguration extends FilterConfiguration {
+    private final int mTpid;
+
+    private TsFilterConfiguration(Settings settings, int tpid) {
+        super(settings);
+        mTpid = tpid;
+    }
+
+    @Override
+    public int getType() {
+        return FilterConfiguration.FILTER_TYPE_TS;
+    }
+
+    /**
+     * Creates a builder for {@link TsFilterConfiguration}.
+     */
+    @NonNull
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link TsFilterConfiguration}.
+     */
+    public static class Builder {
+        private Settings mSettings;
+        private int mTpid;
+
+        /**
+         * Sets filter settings.
+         *
+         * @param settings the filter settings.
+         */
+        @NonNull
+        public Builder setSettings(@NonNull Settings settings) {
+            mSettings = settings;
+            return this;
+        }
+
+        /**
+         * Sets Tag Protocol ID.
+         *
+         * @param tpid the Tag Protocol ID.
+         */
+        @NonNull
+        public Builder setTpid(int tpid) {
+            mTpid = tpid;
+            return this;
+        }
+
+        /**
+         * Builds a {@link TsFilterConfiguration} object.
+         */
+        @NonNull
+        public TsFilterConfiguration build() {
+            return new TsFilterConfiguration(mSettings, mTpid);
+        }
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
index 875b5bd..fa4dd72 100644
--- a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
@@ -16,12 +16,84 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.IntDef;
+import android.media.tv.tuner.Tuner.Filter;
+import android.media.tv.tuner.TunerConstants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
- * TS record event.
+ * Filter event sent from {@link Filter} objects for TS record data.
+ *
  * @hide
  */
 public class TsRecordEvent extends FilterEvent {
-    private int mTpid;
-    private int mIndexMask;
-    private long mByteNumber;
+    /**
+     * @hide
+     */
+    @IntDef(flag = true, value = {
+            TunerConstants.TS_INDEX_FIRST_PACKET,
+            TunerConstants.TS_INDEX_PAYLOAD_UNIT_START_INDICATOR,
+            TunerConstants.TS_INDEX_CHANGE_TO_NOT_SCRAMBLED,
+            TunerConstants.TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED,
+            TunerConstants.TS_INDEX_CHANGE_TO_ODD_SCRAMBLED,
+            TunerConstants.TS_INDEX_DISCONTINUITY_INDICATOR,
+            TunerConstants.TS_INDEX_RANDOM_ACCESS_INDICATOR,
+            TunerConstants.TS_INDEX_PRIORITY_INDICATOR,
+            TunerConstants.TS_INDEX_PCR_FLAG,
+            TunerConstants.TS_INDEX_OPCR_FLAG,
+            TunerConstants.TS_INDEX_SPLICING_POINT_FLAG,
+            TunerConstants.TS_INDEX_PRIVATE_DATA,
+            TunerConstants.TS_INDEX_ADAPTATION_EXTENSION_FLAG,
+            TunerConstants.SC_INDEX_I_FRAME,
+            TunerConstants.SC_INDEX_P_FRAME,
+            TunerConstants.SC_INDEX_B_FRAME,
+            TunerConstants.SC_INDEX_SEQUENCE,
+            TunerConstants.SC_HEVC_INDEX_SPS,
+            TunerConstants.SC_HEVC_INDEX_AUD,
+            TunerConstants.SC_HEVC_INDEX_SLICE_CE_BLA_W_LP,
+            TunerConstants.SC_HEVC_INDEX_SLICE_BLA_W_RADL,
+            TunerConstants.SC_HEVC_INDEX_SLICE_BLA_N_LP,
+            TunerConstants.SC_HEVC_INDEX_SLICE_IDR_W_RADL,
+            TunerConstants.SC_HEVC_INDEX_SLICE_IDR_N_LP,
+            TunerConstants.SC_HEVC_INDEX_SLICE_TRAIL_CRA,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface IndexMask {}
+
+    private final int mPid;
+    private final int mIndexMask;
+    private final long mByteNumber;
+
+    // This constructor is used by JNI code only
+    private TsRecordEvent(int pid, int indexMask, long byteNumber) {
+        mPid = pid;
+        mIndexMask = indexMask;
+        mByteNumber = byteNumber;
+    }
+
+    /**
+     * Gets packet ID.
+     */
+    public int getTpid() {
+        return mPid;
+    }
+
+    /**
+     * Gets index mask.
+     *
+     * <p>The index type is one of TS, SC, and SC-HEVC, and is set when configuring the filter.
+     */
+    @IndexMask
+    public int getIndexMask() {
+        return mIndexMask;
+    }
+
+    /**
+     * Gets the byte number from beginning of the filter's output.
+     */
+    public long getByteNumber() {
+        return mByteNumber;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java
new file mode 100644
index 0000000..2962e98
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+/**
+ * Analog Capabilities.
+ * @hide
+ */
+public class AnalogFrontendCapabilities extends FrontendCapabilities {
+    private final int mTypeCap;
+    private final int mSifStandardCap;
+
+    AnalogFrontendCapabilities(int typeCap, int sifStandardCap) {
+        mTypeCap = typeCap;
+        mSifStandardCap = sifStandardCap;
+    }
+    /**
+     * Gets type capability.
+     */
+    public int getTypeCapability() {
+        return mTypeCap;
+    }
+    /** Gets SIF standard capability. */
+    public int getSifStandardCapability() {
+        return mSifStandardCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
index 16308ce..aec8ce8 100644
--- a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
@@ -16,33 +16,153 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.tv.tuner.V1_0.Constants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
- * Frontend settings for analog.
+ * Frontend settings for analog tuner.
+ *
  * @hide
  */
 public class AnalogFrontendSettings extends FrontendSettings {
-    private int mAnalogType;
-    private int mSifStandard;
+    /** @hide */
+    @IntDef(flag = true, value = {SIGNAL_TYPE_UNDEFINED, SIGNAL_TYPE_PAL, SIGNAL_TYPE_SECAM,
+            SIGNAL_TYPE_NTSC})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SignalType {}
+
+    /**
+     * Undefined analog signal type.
+     */
+    public static final int SIGNAL_TYPE_UNDEFINED = Constants.FrontendAnalogType.UNDEFINED;
+    /**
+     * PAL analog signal type.
+     */
+    public static final int SIGNAL_TYPE_PAL = Constants.FrontendAnalogType.PAL;
+    /**
+     * SECM analog signal type.
+     */
+    public static final int SIGNAL_TYPE_SECAM = Constants.FrontendAnalogType.SECAM;
+    /**
+     * NTSC analog signal type.
+     */
+    public static final int SIGNAL_TYPE_NTSC = Constants.FrontendAnalogType.NTSC;
+
+
+    /** @hide */
+    @IntDef(flag = true, value = {SIF_UNDEFINED, SIF_BG, SIF_BG_A2, SIF_BG_NICAM, SIF_I, SIF_DK,
+            SIF_DK1, SIF_DK2, SIF_DK3, SIF_DK_NICAM, SIF_L, SIF_M, SIF_M_BTSC, SIF_M_A2,
+            SIF_M_EIA_J, SIF_I_NICAM, SIF_L_NICAM, SIF_L_PRIME})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SifStandard {}
+
+    /**
+     * Undefined Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_UNDEFINED = Constants.FrontendAnalogSifStandard.UNDEFINED;
+    /**
+     * BG Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_BG = Constants.FrontendAnalogSifStandard.BG;
+    /**
+     * BG-A2 Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_BG_A2 = Constants.FrontendAnalogSifStandard.BG_A2;
+    /**
+     * BG-NICAM Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_BG_NICAM = Constants.FrontendAnalogSifStandard.BG_NICAM;
+    /**
+     * I Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_I = Constants.FrontendAnalogSifStandard.I;
+    /**
+     * DK Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_DK = Constants.FrontendAnalogSifStandard.DK;
+    /**
+     * DK1 Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_DK1 = Constants.FrontendAnalogSifStandard.DK1;
+    /**
+     * DK2 Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_DK2 = Constants.FrontendAnalogSifStandard.DK2;
+    /**
+     * DK3 Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_DK3 = Constants.FrontendAnalogSifStandard.DK3;
+    /**
+     * DK-NICAM Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_DK_NICAM = Constants.FrontendAnalogSifStandard.DK_NICAM;
+    /**
+     * L Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_L = Constants.FrontendAnalogSifStandard.L;
+    /**
+     * M Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_M = Constants.FrontendAnalogSifStandard.M;
+    /**
+     * M-BTSC Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_M_BTSC = Constants.FrontendAnalogSifStandard.M_BTSC;
+    /**
+     * M-A2 Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_M_A2 = Constants.FrontendAnalogSifStandard.M_A2;
+    /**
+     * M-EIA-J Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_M_EIA_J = Constants.FrontendAnalogSifStandard.M_EIA_J;
+    /**
+     * I-NICAM Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_I_NICAM = Constants.FrontendAnalogSifStandard.I_NICAM;
+    /**
+     * L-NICAM Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_L_NICAM = Constants.FrontendAnalogSifStandard.L_NICAM;
+    /**
+     * L-PRIME Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_L_PRIME = Constants.FrontendAnalogSifStandard.L_PRIME;
+
+
+    private final int mAnalogType;
+    private final int mSifStandard;
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ANALOG;
+        return FrontendSettings.TYPE_ANALOG;
     }
 
+
+    /**
+     * Gets analog signal type.
+     */
+    @SignalType
     public int getAnalogType() {
         return mAnalogType;
     }
 
+    /**
+     * Gets Standard Interchange Format (SIF).
+     */
+    @SifStandard
     public int getSifStandard() {
         return mSifStandard;
     }
 
     /**
-     * Creates a new builder object.
+     * Creates a builder for {@link AnalogFrontendSettings}.
      */
+    @NonNull
     public static Builder newBuilder() {
         return new Builder();
     }
@@ -54,7 +174,7 @@
     }
 
     /**
-     * Builder for FrontendAnalogSettings.
+     * Builder for {@link AnalogFrontendSettings}.
      */
     public static class Builder {
         private int mFrequency;
@@ -64,8 +184,9 @@
         private Builder() {}
 
         /**
-         * Sets frequency.
+         * Sets frequency in Hz.
          */
+        @NonNull
         public Builder setFrequency(int frequency) {
             mFrequency = frequency;
             return this;
@@ -74,22 +195,25 @@
         /**
          * Sets analog type.
          */
-        public Builder setAnalogType(int analogType) {
+        @NonNull
+        public Builder setAnalogType(@SignalType int analogType) {
             mAnalogType = analogType;
             return this;
         }
 
         /**
-         * Sets sif standard.
+         * Sets Standard Interchange Format (SIF).
          */
-        public Builder setSifStandard(int sifStandard) {
+        @NonNull
+        public Builder setSifStandard(@SifStandard int sifStandard) {
             mSifStandard = sifStandard;
             return this;
         }
 
         /**
-         * Builds a FrontendAnalogSettings instance.
+         * Builds a {@link AnalogFrontendSettings} object.
          */
+        @NonNull
         public AnalogFrontendSettings build() {
             return new AnalogFrontendSettings(mFrequency, mAnalogType, mSifStandard);
         }
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java
new file mode 100644
index 0000000..677f9387
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+/**
+ * ATSC-3 Capabilities.
+ * @hide
+ */
+public class Atsc3FrontendCapabilities extends FrontendCapabilities {
+    private final int mBandwidthCap;
+    private final int mModulationCap;
+    private final int mTimeInterleaveModeCap;
+    private final int mCodeRateCap;
+    private final int mFecCap;
+    private final int mDemodOutputFormatCap;
+
+    Atsc3FrontendCapabilities(int bandwidthCap, int modulationCap, int timeInterleaveModeCap,
+            int codeRateCap, int fecCap, int demodOutputFormatCap) {
+        mBandwidthCap = bandwidthCap;
+        mModulationCap = modulationCap;
+        mTimeInterleaveModeCap = timeInterleaveModeCap;
+        mCodeRateCap = codeRateCap;
+        mFecCap = fecCap;
+        mDemodOutputFormatCap = demodOutputFormatCap;
+    }
+
+    /** Gets bandwidth capability. */
+    public int getBandwidthCapability() {
+        return mBandwidthCap;
+    }
+    /** Gets modulation capability. */
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /** Gets time interleave mod capability. */
+    public int getTimeInterleaveModeCapability() {
+        return mTimeInterleaveModeCap;
+    }
+    /** Gets code rate capability. */
+    public int getCodeRateCapability() {
+        return mCodeRateCap;
+    }
+    /** Gets FEC capability. */
+    public int getFecCapability() {
+        return mFecCap;
+    }
+    /** Gets demodulator output format capability. */
+    public int getDemodOutputFormatCapability() {
+        return mDemodOutputFormatCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
index bce8a64..5b09e36 100644
--- a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
@@ -16,10 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
 import java.util.List;
 
 /**
@@ -37,6 +33,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ATSC3;
+        return FrontendSettings.TYPE_ATSC3;
     }
 }
diff --git a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java
similarity index 61%
copy from media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
copy to media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java
index 306de84..6ae3c63 100644
--- a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
+++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java
@@ -14,18 +14,20 @@
  * limitations under the License.
  */
 
-package android.media.tv.tuner.filter;
+package android.media.tv.tuner.frontend;
 
 /**
- * Extra Meta Data from AD (Audio Descriptor) according to
- * ETSI TS 101 154 V2.1.1.
+ * ATSC Capabilities.
  * @hide
  */
-public class AudioExtraMetaData {
-    private byte mAdFade;
-    private byte mAdPan;
-    private byte mVersionTextTag;
-    private byte mAdGainCenter;
-    private byte mAdGainFront;
-    private byte mAdGainSurround;
+public class AtscFrontendCapabilities extends FrontendCapabilities {
+    private final int mModulationCap;
+
+    AtscFrontendCapabilities(int modulationCap) {
+        mModulationCap = modulationCap;
+    }
+    /** Gets modulation capability. */
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
index 14c5cdd..19e18d0 100644
--- a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
@@ -16,9 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Frontend settings for ATSC.
  * @hide
@@ -32,6 +29,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ATSC;
+        return FrontendSettings.TYPE_ATSC;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java
new file mode 100644
index 0000000..edea7af
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+/**
+ * DVBC Capabilities.
+ * @hide
+ */
+public class DvbcFrontendCapabilities extends FrontendCapabilities {
+    private final int mModulationCap;
+    private final int mFecCap;
+    private final int mAnnexCap;
+
+    DvbcFrontendCapabilities(int modulationCap, int fecCap, int annexCap) {
+        mModulationCap = modulationCap;
+        mFecCap = fecCap;
+        mAnnexCap = annexCap;
+    }
+
+    /** Gets modulation capability. */
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /** Gets FEC capability. */
+    public int getFecCapability() {
+        return mFecCap;
+    }
+    /** Gets annex capability. */
+    public int getAnnexCapability() {
+        return mAnnexCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
index 07e49ff..60618f6 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
@@ -16,9 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Frontend settings for DVBC.
  * @hide
@@ -37,6 +34,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_DVBC;
+        return FrontendSettings.TYPE_DVBC;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java
new file mode 100644
index 0000000..f5a4157
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+/**
+ * DVBS Capabilities.
+ * @hide
+ */
+public class DvbsFrontendCapabilities extends FrontendCapabilities {
+    private final int mModulationCap;
+    private final long mInnerFecCap;
+    private final int mStandard;
+
+    DvbsFrontendCapabilities(int modulationCap, long innerFecCap, int standard) {
+        mModulationCap = modulationCap;
+        mInnerFecCap = innerFecCap;
+        mStandard = standard;
+    }
+
+    /** Gets modulation capability. */
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /** Gets inner FEC capability. */
+    public long getInnerFecCapability() {
+        return mInnerFecCap;
+    }
+    /** Gets DVBS standard capability. */
+    public int getStandardCapability() {
+        return mStandard;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
index 23c0a7b1..586787f 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
@@ -16,9 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Frontend settings for DVBS.
  * @hide
@@ -38,6 +35,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_DVBS;
+        return FrontendSettings.TYPE_DVBS;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java
new file mode 100644
index 0000000..e9c16ddd4
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+/**
+ * DVBT Capabilities.
+ * @hide
+ */
+public class DvbtFrontendCapabilities extends FrontendCapabilities {
+    private final int mTransmissionModeCap;
+    private final int mBandwidthCap;
+    private final int mConstellationCap;
+    private final int mCoderateCap;
+    private final int mHierarchyCap;
+    private final int mGuardIntervalCap;
+    private final boolean mIsT2Supported;
+    private final boolean mIsMisoSupported;
+
+    DvbtFrontendCapabilities(int transmissionModeCap, int bandwidthCap, int constellationCap,
+            int coderateCap, int hierarchyCap, int guardIntervalCap, boolean isT2Supported,
+            boolean isMisoSupported) {
+        mTransmissionModeCap = transmissionModeCap;
+        mBandwidthCap = bandwidthCap;
+        mConstellationCap = constellationCap;
+        mCoderateCap = coderateCap;
+        mHierarchyCap = hierarchyCap;
+        mGuardIntervalCap = guardIntervalCap;
+        mIsT2Supported = isT2Supported;
+        mIsMisoSupported = isMisoSupported;
+    }
+
+    /** Gets transmission mode capability. */
+    public int getTransmissionModeCapability() {
+        return mTransmissionModeCap;
+    }
+    /** Gets bandwidth capability. */
+    public int getBandwidthCapability() {
+        return mBandwidthCap;
+    }
+    /** Gets constellation capability. */
+    public int getConstellationCapability() {
+        return mConstellationCap;
+    }
+    /** Gets code rate capability. */
+    public int getCodeRateCapability() {
+        return mCoderateCap;
+    }
+    /** Gets hierarchy capability. */
+    public int getHierarchyCapability() {
+        return mHierarchyCap;
+    }
+    /** Gets guard interval capability. */
+    public int getGuardIntervalCapability() {
+        return mGuardIntervalCap;
+    }
+    /** Returns whether T2 is supported. */
+    public boolean getIsT2Supported() {
+        return mIsT2Supported;
+    }
+    /** Returns whether MISO is supported. */
+    public boolean getIsMisoSupported() {
+        return mIsMisoSupported;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
index eec00f3..6b350a7 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
@@ -16,10 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Frontend settings for DVBT.
  * @hide
@@ -45,6 +41,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_DVBT;
+        return FrontendSettings.TYPE_DVBT;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendCallback.java b/media/java/android/media/tv/tuner/frontend/FrontendCallback.java
index 91776e1..0992eb6 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendCallback.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendCallback.java
@@ -16,8 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.ScanMessage;
-
 /**
  * Frontend Callback.
  *
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java
similarity index 81%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java
index fb5d836..7350bc0 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java
@@ -14,6 +14,11 @@
  * limitations under the License.
  */
 
-package android.media;
+package android.media.tv.tuner.frontend;
 
-parcelable RouteSessionInfo;
+/**
+ * Frontend Capabilities.
+ * @hide
+ */
+public abstract class FrontendCapabilities {
+}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
index ef6c029..99e8dd2 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
@@ -16,8 +16,7 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendCapabilities;
-import android.media.tv.tuner.TunerConstants.FrontendType;
+import android.media.tv.tuner.frontend.FrontendSettings.Type;
 
 /**
  * Frontend info.
@@ -55,7 +54,7 @@
         return mId;
     }
     /** Gets frontend type. */
-    @FrontendType
+    @Type
     public int getType() {
         return mType;
     }
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
new file mode 100644
index 0000000..210aef4
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.hardware.tv.tuner.V1_0.Constants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Frontend settings for tune and scan operations.
+ *
+ * @hide
+ */
+public abstract class FrontendSettings {
+    /** @hide */
+    @IntDef({TYPE_UNDEFINED, TYPE_ANALOG, TYPE_ATSC, TYPE_ATSC3, TYPE_DVBC, TYPE_DVBS, TYPE_DVBT,
+            TYPE_ISDBS, TYPE_ISDBS3, TYPE_ISDBT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
+    /**
+     * Undefined frontend type.
+     */
+    public static final int TYPE_UNDEFINED = Constants.FrontendType.UNDEFINED;
+    /**
+     * Analog frontend type.
+     */
+    public static final int TYPE_ANALOG = Constants.FrontendType.ANALOG;
+    /**
+     * Advanced Television Systems Committee (ATSC) frontend type.
+     */
+    public static final int TYPE_ATSC = Constants.FrontendType.ATSC;
+    /**
+     * Advanced Television Systems Committee 3.0 (ATSC-3) frontend type.
+     */
+    public static final int TYPE_ATSC3 = Constants.FrontendType.ATSC3;
+    /**
+     * Digital Video Broadcasting-Cable (DVB-C) frontend type.
+     */
+    public static final int TYPE_DVBC = Constants.FrontendType.DVBC;
+    /**
+     * Digital Video Broadcasting-Satellite (DVB-S) frontend type.
+     */
+    public static final int TYPE_DVBS = Constants.FrontendType.DVBS;
+    /**
+     * Digital Video Broadcasting-Terrestrial (DVB-T) frontend type.
+     */
+    public static final int TYPE_DVBT = Constants.FrontendType.DVBT;
+    /**
+     * Integrated Services Digital Broadcasting-Satellite (ISDB-S) frontend type.
+     */
+    public static final int TYPE_ISDBS = Constants.FrontendType.ISDBS;
+    /**
+     * Integrated Services Digital Broadcasting-Satellite 3 (ISDB-S3) frontend type.
+     */
+    public static final int TYPE_ISDBS3 = Constants.FrontendType.ISDBS3;
+    /**
+     * Integrated Services Digital Broadcasting-Terrestrial (ISDB-T) frontend type.
+     */
+    public static final int TYPE_ISDBT = Constants.FrontendType.ISDBT;
+
+    private final int mFrequency;
+
+    /** @hide */
+    public FrontendSettings(int frequency) {
+        mFrequency = frequency;
+    }
+
+    /**
+     * Returns the frontend type.
+     */
+    @Type
+    public abstract int getType();
+
+    /**
+     * Gets the frequency.
+     *
+     * @return the frequency in Hz.
+     */
+    public int getFrequency() {
+        return mFrequency;
+    }
+
+}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
index 89ec536..fb5d62a 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -16,13 +16,13 @@
 
 package android.media.tv.tuner.frontend;
 
+import android.media.tv.tuner.Lnb;
 import android.media.tv.tuner.TunerConstants;
 import android.media.tv.tuner.TunerConstants.FrontendDvbcSpectralInversion;
 import android.media.tv.tuner.TunerConstants.FrontendDvbtHierarchy;
 import android.media.tv.tuner.TunerConstants.FrontendInnerFec;
 import android.media.tv.tuner.TunerConstants.FrontendModulation;
 import android.media.tv.tuner.TunerConstants.FrontendStatusType;
-import android.media.tv.tuner.TunerConstants.LnbVoltage;
 
 /**
  * Frontend status
@@ -128,7 +128,7 @@
         return (int) mValue;
     }
     /** Power Voltage Type for LNB. */
-    @LnbVoltage
+    @Lnb.Voltage
     public int getLnbVoltage() {
         if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LNB_VOLTAGE) {
             throw new IllegalStateException();
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbcFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/IsdbcFrontendCapabilities.java
new file mode 100644
index 0000000..6544b17
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/IsdbcFrontendCapabilities.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+/**
+ * ISDBC Capabilities.
+ * @hide
+ */
+public class IsdbcFrontendCapabilities extends FrontendCapabilities {
+    private final int mModeCap;
+    private final int mBandwidthCap;
+    private final int mModulationCap;
+    private final int mCoderateCap;
+    private final int mGuardIntervalCap;
+
+    IsdbcFrontendCapabilities(int modeCap, int bandwidthCap, int modulationCap, int coderateCap,
+            int guardIntervalCap) {
+        mModeCap = modeCap;
+        mBandwidthCap = bandwidthCap;
+        mModulationCap = modulationCap;
+        mCoderateCap = coderateCap;
+        mGuardIntervalCap = guardIntervalCap;
+    }
+
+    /** Gets mode capability. */
+    public int getModeCapability() {
+        return mModeCap;
+    }
+    /** Gets bandwidth capability. */
+    public int getBandwidthCapability() {
+        return mBandwidthCap;
+    }
+    /** Gets modulation capability. */
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /** Gets code rate capability. */
+    public int getCodeRateCapability() {
+        return mCoderateCap;
+    }
+    /** Gets guard interval capability. */
+    public int getGuardIntervalCapability() {
+        return mGuardIntervalCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java
new file mode 100644
index 0000000..92832b7
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+/**
+ * ISDBS-3 Capabilities.
+ * @hide
+ */
+public class Isdbs3FrontendCapabilities extends FrontendCapabilities {
+    private final int mModulationCap;
+    private final int mCoderateCap;
+
+    Isdbs3FrontendCapabilities(int modulationCap, int coderateCap) {
+        mModulationCap = modulationCap;
+        mCoderateCap = coderateCap;
+    }
+
+    /** Gets modulation capability. */
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /** Gets code rate capability. */
+    public int getCodeRateCapability() {
+        return mCoderateCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
index 736d0b1..45932a7 100644
--- a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
@@ -16,9 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Frontend settings for ISDBS-3.
  * @hide
@@ -37,6 +34,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ISDBS3;
+        return FrontendSettings.TYPE_ISDBS3;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java
new file mode 100644
index 0000000..b930b25
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+/**
+ * ISDBS Capabilities.
+ * @hide
+ */
+public class IsdbsFrontendCapabilities extends FrontendCapabilities {
+    private final int mModulationCap;
+    private final int mCoderateCap;
+
+    IsdbsFrontendCapabilities(int modulationCap, int coderateCap) {
+        mModulationCap = modulationCap;
+        mCoderateCap = coderateCap;
+    }
+
+    /** Gets modulation capability. */
+    public int getModulationCapability() {
+        return mModulationCap;
+    }
+    /** Gets code rate capability. */
+    public int getCodeRateCapability() {
+        return mCoderateCap;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
index 7fd5da7..e726a9a 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
@@ -16,9 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Frontend settings for ISDBS.
  * @hide
@@ -37,6 +34,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ISDBS;
+        return FrontendSettings.TYPE_ISDBS;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
index 3f83267..f2b7d24 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
@@ -16,10 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Frontend settings for ISDBT.
  * @hide
@@ -37,6 +33,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ISDBT;
+        return FrontendSettings.TYPE_ISDBT;
     }
 }
diff --git a/media/java/android/media/tv/tuner/ScanMessage.java b/media/java/android/media/tv/tuner/frontend/ScanMessage.java
similarity index 61%
rename from media/java/android/media/tv/tuner/ScanMessage.java
rename to media/java/android/media/tv/tuner/frontend/ScanMessage.java
index 35f54f8..dd687dd 100644
--- a/media/java/android/media/tv/tuner/ScanMessage.java
+++ b/media/java/android/media/tv/tuner/frontend/ScanMessage.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,9 +14,13 @@
  * limitations under the License.
  */
 
-package android.media.tv.tuner;
+package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.TunerConstants.ScanMessageType;
+import android.annotation.IntDef;
+import android.hardware.tv.tuner.V1_0.Constants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Message from frontend during scan operations.
@@ -24,6 +28,43 @@
  * @hide
  */
 public class ScanMessage {
+
+    /** @hide */
+    @IntDef({
+        LOCKED,
+        END,
+        PROGRESS_PERCENT,
+        FREQUENCY,
+        SYMBOL_RATE,
+        PLP_IDS,
+        GROUP_IDS,
+        INPUT_STREAM_IDS,
+        STANDARD,
+        ATSC3_PLP_INFO
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+    /** @hide */
+    public static final int LOCKED = Constants.FrontendScanMessageType.LOCKED;
+    /** @hide */
+    public static final int END = Constants.FrontendScanMessageType.END;
+    /** @hide */
+    public static final int PROGRESS_PERCENT = Constants.FrontendScanMessageType.PROGRESS_PERCENT;
+    /** @hide */
+    public static final int FREQUENCY = Constants.FrontendScanMessageType.FREQUENCY;
+    /** @hide */
+    public static final int SYMBOL_RATE = Constants.FrontendScanMessageType.SYMBOL_RATE;
+    /** @hide */
+    public static final int PLP_IDS = Constants.FrontendScanMessageType.PLP_IDS;
+    /** @hide */
+    public static final int GROUP_IDS = Constants.FrontendScanMessageType.GROUP_IDS;
+    /** @hide */
+    public static final int INPUT_STREAM_IDS = Constants.FrontendScanMessageType.INPUT_STREAM_IDS;
+    /** @hide */
+    public static final int STANDARD = Constants.FrontendScanMessageType.STANDARD;
+    /** @hide */
+    public static final int ATSC3_PLP_INFO = Constants.FrontendScanMessageType.ATSC3_PLP_INFO;
+
     private final int mType;
     private final Object mValue;
 
@@ -33,69 +74,69 @@
     }
 
     /** Gets scan message type. */
-    @ScanMessageType
+    @Type
     public int getMessageType() {
         return mType;
     }
     /** Message indicates whether frontend is locked or not. */
     public boolean getIsLocked() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_LOCKED) {
+        if (mType != 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) {
+        if (mType != END) {
             throw new IllegalStateException();
         }
         return (Boolean) mValue;
     }
     /** Progress message in percent. */
     public int getProgressPercent() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_PROGRESS_PERCENT) {
+        if (mType != PROGRESS_PERCENT) {
             throw new IllegalStateException();
         }
         return (Integer) mValue;
     }
     /** Gets frequency. */
     public int getFrequency() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_FREQUENCY) {
+        if (mType != FREQUENCY) {
             throw new IllegalStateException();
         }
         return (Integer) mValue;
     }
     /** Gets symbol rate. */
     public int getSymbolRate() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_SYMBOL_RATE) {
+        if (mType != SYMBOL_RATE) {
             throw new IllegalStateException();
         }
         return (Integer) mValue;
     }
     /** Gets PLP IDs. */
     public int[] getPlpIds() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_PLP_IDS) {
+        if (mType != PLP_IDS) {
             throw new IllegalStateException();
         }
         return (int[]) mValue;
     }
     /** Gets group IDs. */
     public int[] getGroupIds() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_GROUP_IDS) {
+        if (mType != GROUP_IDS) {
             throw new IllegalStateException();
         }
         return (int[]) mValue;
     }
     /** Gets Input stream IDs. */
     public int[] getInputStreamIds() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_INPUT_STREAM_IDS) {
+        if (mType != 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) {
+        if (mType != STANDARD) {
             throw new IllegalStateException();
         }
         return (int) mValue;
@@ -103,7 +144,7 @@
 
     /** Gets PLP information for ATSC3. */
     public Atsc3PlpInfo[] getAtsc3PlpInfos() {
-        if (mType != TunerConstants.SCAN_MESSAGE_TYPE_ATSC3_PLP_INFO) {
+        if (mType != ATSC3_PLP_INFO) {
             throw new IllegalStateException();
         }
         return (Atsc3PlpInfo[]) mValue;
diff --git a/media/java/android/mtp/MtpPropertyList.java b/media/java/android/mtp/MtpPropertyList.java
index 557f099..53d838d 100644
--- a/media/java/android/mtp/MtpPropertyList.java
+++ b/media/java/android/mtp/MtpPropertyList.java
@@ -16,7 +16,8 @@
 
 package android.mtp;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
 import java.util.ArrayList;
 import java.util.List;
 
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
index c7dbca6..ba75263 100644
--- a/media/java/android/mtp/MtpStorage.java
+++ b/media/java/android/mtp/MtpStorage.java
@@ -16,9 +16,8 @@
 
 package android.mtp;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.storage.StorageVolume;
-import android.provider.MediaStore;
 
 /**
  * This class represents a storage unit on an MTP device.
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index 86a1076..06adf30 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -21,8 +21,8 @@
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.UnsupportedAppUsage;
 import android.app.Service;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index ee67613..536a061 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -143,6 +143,10 @@
         "libutils",
     ],
 
+    header_libs: [
+        "libstagefright_foundation_headers",
+    ],
+
     export_include_dirs: ["."],
 
     cflags: [
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index f0f3688..4f1125f 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -696,6 +696,10 @@
     return tuner->openFilter(filterType, bufferSize);
 }
 
+static jobject android_media_tv_Tuner_open_time_filter(JNIEnv, jobject) {
+    return NULL;
+}
+
 static DemuxFilterSettings getFilterSettings(
         JNIEnv *env, int type, int subtype, jobject filterSettingsObj) {
     DemuxFilterSettings filterSettings;
@@ -840,6 +844,28 @@
     return 0;
 }
 
+// TODO: implement TimeFilter functions
+static int android_media_tv_Tuner_time_filter_set_timestamp(
+        JNIEnv, jobject, jlong) {
+    return 0;
+}
+
+static int android_media_tv_Tuner_time_filter_clear_timestamp(JNIEnv, jobject) {
+    return 0;
+}
+
+static jobject android_media_tv_Tuner_time_filter_get_timestamp(JNIEnv, jobject) {
+    return NULL;
+}
+
+static jobject android_media_tv_Tuner_time_filter_get_source_time(JNIEnv, jobject) {
+    return NULL;
+}
+
+static int android_media_tv_Tuner_time_filter_close(JNIEnv, jobject) {
+    return 0;
+}
+
 static jobject android_media_tv_Tuner_open_descrambler(JNIEnv *env, jobject thiz) {
     sp<JTuner> tuner = getTuner(env, thiz);
     return tuner->openDescrambler();
@@ -1119,6 +1145,8 @@
             (void *)android_media_tv_Tuner_get_frontend_info },
     { "nativeOpenFilter", "(III)Landroid/media/tv/tuner/Tuner$Filter;",
             (void *)android_media_tv_Tuner_open_filter },
+    { "nativeOpenTimeFilter", "()Landroid/media/tv/tuner/Tuner$TimeFilter;",
+            (void *)android_media_tv_Tuner_open_time_filter },
     { "nativeGetLnbIds", "()Ljava/util/List;",
             (void *)android_media_tv_Tuner_get_lnb_ids },
     { "nativeOpenLnbById", "(I)Landroid/media/tv/tuner/Tuner$Lnb;",
@@ -1144,6 +1172,16 @@
     { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_filter },
 };
 
+static const JNINativeMethod gTimeFilterMethods[] = {
+    { "nativeSetTimeStamp", "(J)I", (void *)android_media_tv_Tuner_time_filter_set_timestamp },
+    { "nativeClearTimeStamp", "()I", (void *)android_media_tv_Tuner_time_filter_clear_timestamp },
+    { "nativeGetTimeStamp", "()Ljava/lang/Long;",
+            (void *)android_media_tv_Tuner_time_filter_get_timestamp },
+    { "nativeGetSourceTime", "()Ljava/lang/Long;",
+            (void *)android_media_tv_Tuner_time_filter_get_source_time },
+    { "nativeClose", "()I", (void *)android_media_tv_Tuner_time_filter_close },
+};
+
 static const JNINativeMethod gDescramblerMethods[] = {
     { "nativeAddPid", "(IILandroid/media/tv/tuner/Tuner$Filter;)I",
             (void *)android_media_tv_Tuner_add_pid },
@@ -1194,6 +1232,13 @@
         return false;
     }
     if (AndroidRuntime::registerNativeMethods(
+            env, "android/media/tv/tuner/Tuner$TimeFilter",
+            gTimeFilterMethods,
+            NELEM(gTimeFilterMethods)) != JNI_OK) {
+        ALOGE("Failed to register time filter native methods");
+        return false;
+    }
+    if (AndroidRuntime::registerNativeMethods(
             env, "android/media/tv/tuner/Tuner$Descrambler",
             gDescramblerMethods,
             NELEM(gDescramblerMethods)) != JNI_OK) {
diff --git a/media/mca/effect/java/android/media/effect/SingleFilterEffect.java b/media/mca/effect/java/android/media/effect/SingleFilterEffect.java
index dfbf5d2..121443f 100644
--- a/media/mca/effect/java/android/media/effect/SingleFilterEffect.java
+++ b/media/mca/effect/java/android/media/effect/SingleFilterEffect.java
@@ -17,12 +17,11 @@
 
 package android.media.effect;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.filterfw.core.Filter;
 import android.filterfw.core.FilterFactory;
 import android.filterfw.core.FilterFunction;
 import android.filterfw.core.Frame;
-import android.media.effect.EffectContext;
 
 /**
  * Effect subclass for effects based on a single Filter. Subclasses need only invoke the
diff --git a/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java
index 52615bf..3a7f1ed 100644
--- a/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java
+++ b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java
@@ -17,11 +17,11 @@
 
 package android.filterfw;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.filterfw.core.AsyncRunner;
-import android.filterfw.core.FilterGraph;
 import android.filterfw.core.FilterContext;
+import android.filterfw.core.FilterGraph;
 import android.filterfw.core.FrameManager;
 import android.filterfw.core.GraphRunner;
 import android.filterfw.core.RoundRobinScheduler;
diff --git a/media/mca/filterfw/java/android/filterfw/core/Filter.java b/media/mca/filterfw/java/android/filterfw/core/Filter.java
index 4f56b92..a608ef5 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Filter.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Filter.java
@@ -17,19 +17,15 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.FilterContext;
-import android.filterfw.core.FilterPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.io.TextGraphReader;
-import android.filterfw.io.GraphIOException;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.filterfw.format.ObjectFormat;
+import android.filterfw.io.GraphIOException;
+import android.filterfw.io.TextGraphReader;
 import android.util.Log;
 
 import java.io.Serializable;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Field;
-import java.lang.Thread;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterContext.java b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java
index a19220e..6b0a219 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FilterContext.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java
@@ -17,11 +17,7 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Filter;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameManager;
-import android.filterfw.core.GLEnvironment;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import java.util.HashMap;
 import java.util.HashSet;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java
index e6ca11f..35a298f 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java
@@ -17,6 +17,11 @@
 
 package android.filterfw.core;
 
+import android.compat.annotation.UnsupportedAppUsage;
+import android.filterpacks.base.FrameBranch;
+import android.filterpacks.base.NullFilter;
+import android.util.Log;
+
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -25,14 +30,6 @@
 import java.util.Set;
 import java.util.Stack;
 
-import android.filterfw.core.FilterContext;
-import android.filterfw.core.KeyValueMap;
-import android.filterpacks.base.FrameBranch;
-import android.filterpacks.base.NullFilter;
-
-import android.annotation.UnsupportedAppUsage;
-import android.util.Log;
-
 /**
  * @hide
  */
diff --git a/media/mca/filterfw/java/android/filterfw/core/Frame.java b/media/mca/filterfw/java/android/filterfw/core/Frame.java
index e880783..c4d935a 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Frame.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Frame.java
@@ -17,9 +17,7 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.FrameManager;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Bitmap;
 
 import java.nio.ByteBuffer;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java
index eb0ff0a..a87e9b9 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java
@@ -17,9 +17,7 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.MutableFrameFormat;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import java.util.Arrays;
 import java.util.Map.Entry;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameManager.java b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java
index 85c8fcd..e49aaf1 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FrameManager.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java
@@ -17,10 +17,7 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.MutableFrameFormat;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * @hide
diff --git a/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java
index e25d6a7..7e4e8a6 100644
--- a/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java
+++ b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java
@@ -17,13 +17,12 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.NativeAllocatorTag;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.SurfaceTexture;
+import android.media.MediaRecorder;
 import android.os.Looper;
 import android.util.Log;
 import android.view.Surface;
-import android.media.MediaRecorder;
 
 /**
  * @hide
diff --git a/media/mca/filterfw/java/android/filterfw/core/GLFrame.java b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java
index 9e3025f..1ccd7fe 100644
--- a/media/mca/filterfw/java/android/filterfw/core/GLFrame.java
+++ b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java
@@ -17,15 +17,10 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.FrameManager;
-import android.filterfw.core.NativeFrame;
-import android.filterfw.core.StopWatchMap;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Bitmap;
-import android.opengl.GLES20;
 import android.graphics.Rect;
+import android.opengl.GLES20;
 
 import java.nio.ByteBuffer;
 
diff --git a/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java
index 250cfaa..b57e8bb 100644
--- a/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java
+++ b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java
@@ -17,7 +17,7 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * @hide
diff --git a/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java
index ae2ad99..da00b1f 100644
--- a/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java
+++ b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java
@@ -17,9 +17,7 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.KeyValueMap;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import java.util.Arrays;
 
diff --git a/media/mca/filterfw/java/android/filterfw/core/Program.java b/media/mca/filterfw/java/android/filterfw/core/Program.java
index 376c085..145388e 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Program.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Program.java
@@ -17,8 +17,7 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Frame;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * @hide
diff --git a/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java
index f41636e..e043be0 100644
--- a/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java
+++ b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java
@@ -17,12 +17,7 @@
 
 package android.filterfw.core;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Frame;
-import android.filterfw.core.NativeAllocatorTag;
-import android.filterfw.core.Program;
-import android.filterfw.core.StopWatchMap;
-import android.filterfw.core.VertexFrame;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.filterfw.geometry.Quad;
 import android.opengl.GLES20;
 
diff --git a/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java
index ac08730..0e05092 100644
--- a/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java
+++ b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java
@@ -17,7 +17,7 @@
 
 package android.filterfw.format;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.filterfw.core.FrameFormat;
 import android.filterfw.core.MutableFrameFormat;
 import android.graphics.Bitmap;
diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Point.java b/media/mca/filterfw/java/android/filterfw/geometry/Point.java
index d7acf12..96d2d7b 100644
--- a/media/mca/filterfw/java/android/filterfw/geometry/Point.java
+++ b/media/mca/filterfw/java/android/filterfw/geometry/Point.java
@@ -17,8 +17,7 @@
 
 package android.filterfw.geometry;
 
-import android.annotation.UnsupportedAppUsage;
-import java.lang.Math;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * @hide
diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Quad.java b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java
index 610e5b8..2b308a9 100644
--- a/media/mca/filterfw/java/android/filterfw/geometry/Quad.java
+++ b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java
@@ -17,10 +17,8 @@
 
 package android.filterfw.geometry;
 
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.geometry.Point;
+import android.compat.annotation.UnsupportedAppUsage;
 
-import java.lang.Float;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
index 04fccc7..221783b 100644
--- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
@@ -16,14 +16,13 @@
 
 package com.android.mediarouteprovider.example;
 
-import static android.media.MediaRoute2Info.DEVICE_TYPE_SPEAKER;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_TV;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_TV;
 
 import android.content.Intent;
 import android.media.MediaRoute2Info;
-import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2ProviderService;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
 import android.os.IBinder;
 import android.text.TextUtils;
 
@@ -46,8 +45,8 @@
     public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to";
     public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to";
 
-    public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
-    public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
+    public static final String ROUTE_ID_SPECIAL_FEATURE = "route_special_feature";
+    public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route";
 
     public static final int VOLUME_MAX = 100;
     public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume";
@@ -58,49 +57,49 @@
     public static final String ACTION_REMOVE_ROUTE =
             "com.android.mediarouteprovider.action_remove_route";
 
-    public static final String CATEGORY_SAMPLE =
-            "com.android.mediarouteprovider.CATEGORY_SAMPLE";
-    public static final String CATEGORY_SPECIAL =
-            "com.android.mediarouteprovider.CATEGORY_SPECIAL";
+    public static final String FEATURE_SAMPLE =
+            "com.android.mediarouteprovider.FEATURE_SAMPLE";
+    public static final String FEATURE_SPECIAL =
+            "com.android.mediarouteprovider.FEATURE_SPECIAL";
 
     Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
-    Map<String, Integer> mRouteSessionMap = new HashMap<>();
+    Map<String, String> mRouteIdToSessionId = new HashMap<>();
     private int mNextSessionId = 1000;
 
     private void initializeRoutes() {
         MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1)
-                .addSupportedCategory(CATEGORY_SAMPLE)
-                .setDeviceType(DEVICE_TYPE_TV)
+                .addFeature(FEATURE_SAMPLE)
+                .setDeviceType(DEVICE_TYPE_REMOTE_TV)
                 .build();
         MediaRoute2Info route2 = new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2)
-                .addSupportedCategory(CATEGORY_SAMPLE)
-                .setDeviceType(DEVICE_TYPE_SPEAKER)
+                .addFeature(FEATURE_SAMPLE)
+                .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER)
                 .build();
         MediaRoute2Info route3 = new MediaRoute2Info.Builder(
                 ROUTE_ID3_SESSION_CREATION_FAILED, ROUTE_NAME3)
-                .addSupportedCategory(CATEGORY_SAMPLE)
+                .addFeature(FEATURE_SAMPLE)
                 .build();
         MediaRoute2Info route4 = new MediaRoute2Info.Builder(
                 ROUTE_ID4_TO_SELECT_AND_DESELECT, ROUTE_NAME4)
-                .addSupportedCategory(CATEGORY_SAMPLE)
+                .addFeature(FEATURE_SAMPLE)
                 .build();
         MediaRoute2Info route5 = new MediaRoute2Info.Builder(
                 ROUTE_ID5_TO_TRANSFER_TO, ROUTE_NAME5)
-                .addSupportedCategory(CATEGORY_SAMPLE)
+                .addFeature(FEATURE_SAMPLE)
                 .build();
         MediaRoute2Info routeSpecial =
-                new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_CATEGORY, ROUTE_NAME_SPECIAL_CATEGORY)
-                        .addSupportedCategory(CATEGORY_SAMPLE)
-                        .addSupportedCategory(CATEGORY_SPECIAL)
+                new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_FEATURE, ROUTE_NAME_SPECIAL_FEATURE)
+                        .addFeature(FEATURE_SAMPLE)
+                        .addFeature(FEATURE_SPECIAL)
                         .build();
         MediaRoute2Info fixedVolumeRoute =
                 new MediaRoute2Info.Builder(ROUTE_ID_FIXED_VOLUME, ROUTE_NAME_FIXED_VOLUME)
-                        .addSupportedCategory(CATEGORY_SAMPLE)
+                        .addFeature(FEATURE_SAMPLE)
                         .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_FIXED)
                         .build();
         MediaRoute2Info variableVolumeRoute =
                 new MediaRoute2Info.Builder(ROUTE_ID_VARIABLE_VOLUME, ROUTE_NAME_VARIABLE_VOLUME)
-                        .addSupportedCategory(CATEGORY_SAMPLE)
+                        .addFeature(FEATURE_SAMPLE)
                         .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
                         .setVolumeMax(VOLUME_MAX)
                         .build();
@@ -154,6 +153,7 @@
 
     @Override
     public void onUpdateVolume(String routeId, int delta) {
+        android.util.Log.d(TAG, "onUpdateVolume routeId= " + routeId + "delta=" + delta);
         MediaRoute2Info route = mRoutes.get(routeId);
         if (route == null) {
             return;
@@ -167,7 +167,7 @@
     }
 
     @Override
-    public void onCreateSession(String packageName, String routeId, String controlCategory,
+    public void onCreateSession(String packageName, String routeId, String routeFeature,
             long requestId) {
         MediaRoute2Info route = mRoutes.get(routeId);
         if (route == null || TextUtils.equals(ROUTE_ID3_SESSION_CREATION_FAILED, routeId)) {
@@ -177,16 +177,16 @@
         }
         maybeDeselectRoute(routeId);
 
-        final int sessionId = mNextSessionId;
+        final String sessionId = String.valueOf(mNextSessionId);
         mNextSessionId++;
 
         mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
                 .setClientPackageName(packageName)
                 .build());
-        mRouteSessionMap.put(routeId, sessionId);
+        mRouteIdToSessionId.put(routeId, sessionId);
 
-        RouteSessionInfo sessionInfo = new RouteSessionInfo.Builder(
-                sessionId, packageName, controlCategory)
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                sessionId, packageName, routeFeature)
                 .addSelectedRoute(routeId)
                 .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
                 .addTransferrableRoute(ROUTE_ID5_TO_TRANSFER_TO)
@@ -196,9 +196,9 @@
     }
 
     @Override
-    public void onDestroySession(int sessionId, RouteSessionInfo lastSessionInfo) {
+    public void onDestroySession(String sessionId, RoutingSessionInfo lastSessionInfo) {
         for (String routeId : lastSessionInfo.getSelectedRoutes()) {
-            mRouteSessionMap.remove(routeId);
+            mRouteIdToSessionId.remove(routeId);
             MediaRoute2Info route = mRoutes.get(routeId);
             if (route != null) {
                 mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
@@ -209,8 +209,8 @@
     }
 
     @Override
-    public void onSelectRoute(int sessionId, String routeId) {
-        RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+    public void onSelectRoute(String sessionId, String routeId) {
+        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
         MediaRoute2Info route = mRoutes.get(routeId);
         if (route == null || sessionInfo == null) {
             return;
@@ -218,11 +218,11 @@
         maybeDeselectRoute(routeId);
 
         mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
-                .setClientPackageName(sessionInfo.getPackageName())
+                .setClientPackageName(sessionInfo.getClientPackageName())
                 .build());
-        mRouteSessionMap.put(routeId, sessionId);
+        mRouteIdToSessionId.put(routeId, sessionId);
 
-        RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
                 .addSelectedRoute(routeId)
                 .removeSelectableRoute(routeId)
                 .addDeselectableRoute(routeId)
@@ -232,19 +232,26 @@
     }
 
     @Override
-    public void onDeselectRoute(int sessionId, String routeId) {
-        RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+    public void onDeselectRoute(String sessionId, String routeId) {
+        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
         MediaRoute2Info route = mRoutes.get(routeId);
 
-        mRouteSessionMap.remove(routeId);
-        if (sessionInfo == null || route == null) {
+        if (sessionInfo == null || route == null
+                || !sessionInfo.getSelectedRoutes().contains(routeId)) {
             return;
         }
+
+        mRouteIdToSessionId.remove(routeId);
         mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
                 .setClientPackageName(null)
                 .build());
 
-        RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+        if (sessionInfo.getSelectedRoutes().size() == 1) {
+            releaseSession(sessionId);
+            return;
+        }
+
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
                 .removeSelectedRoute(routeId)
                 .addSelectableRoute(routeId)
                 .removeDeselectableRoute(routeId)
@@ -254,9 +261,9 @@
     }
 
     @Override
-    public void onTransferToRoute(int sessionId, String routeId) {
-        RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
-        RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+    public void onTransferToRoute(String sessionId, String routeId) {
+        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
                 .clearSelectedRoutes()
                 .addSelectedRoute(routeId)
                 .removeDeselectableRoute(routeId)
@@ -267,18 +274,15 @@
     }
 
     void maybeDeselectRoute(String routeId) {
-        if (!mRouteSessionMap.containsKey(routeId)) {
+        if (!mRouteIdToSessionId.containsKey(routeId)) {
             return;
         }
 
-        int sessionId = mRouteSessionMap.get(routeId);
+        String sessionId = mRouteIdToSessionId.get(routeId);
         onDeselectRoute(sessionId, routeId);
     }
 
     void publishRoutes() {
-        MediaRoute2ProviderInfo info = new MediaRoute2ProviderInfo.Builder()
-                .addRoutes(mRoutes.values())
-                .build();
-        updateProviderInfo(info);
+        notifyRoutes(mRoutes.values());
     }
 }
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
index 6fe847b..007229a 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
@@ -18,21 +18,21 @@
 
 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.DEVICE_TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_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.FEATURES_ALL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURES_SPECIAL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURE_SAMPLE;
+import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURE_SPECIAL;
 import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID1;
 import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID2;
 import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID3_SESSION_CREATION_FAILED;
 import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID4_TO_SELECT_AND_DESELECT;
 import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID5_TO_TRANSFER_TO;
-import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_CATEGORY;
+import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_FEATURE;
 import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_VARIABLE_VOLUME;
 import static com.android.mediaroutertest.MediaRouterManagerTest.SYSTEM_PROVIDER_ID;
 
@@ -48,9 +48,10 @@
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2;
 import android.media.MediaRouter2.RouteCallback;
-import android.media.MediaRouter2.RouteSessionController;
+import android.media.MediaRouter2.RoutingController;
 import android.media.MediaRouter2.SessionCallback;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
 import android.net.Uri;
 import android.os.Parcel;
 import android.support.test.InstrumentationRegistry;
@@ -95,14 +96,14 @@
     }
 
     /**
-     * Tests if we get proper routes for application that has special control category.
+     * Tests if we get proper routes for application that has special route type.
      */
     @Test
     public void testGetRoutes() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_SPECIAL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_SPECIAL);
 
         assertEquals(1, routes.size());
-        assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
+        assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
     }
 
     @Test
@@ -114,9 +115,9 @@
                 .setIconUri(new Uri.Builder().path("icon").build())
                 .setVolume(5)
                 .setVolumeMax(20)
-                .addSupportedCategory(CATEGORY_SAMPLE)
+                .addFeature(FEATURE_SAMPLE)
                 .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
-                .setDeviceType(DEVICE_TYPE_SPEAKER)
+                .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER)
                 .build();
 
         MediaRoute2Info routeInfoRebuilt = new MediaRoute2Info.Builder(routeInfo).build();
@@ -137,21 +138,13 @@
                 .setClientPackageName("com.android.mediaroutertest")
                 .setConnectionState(CONNECTION_STATE_CONNECTING)
                 .setIconUri(new Uri.Builder().path("icon").build())
-                .addSupportedCategory(CATEGORY_SAMPLE)
+                .addFeature(FEATURE_SAMPLE)
                 .setVolume(5)
                 .setVolumeMax(20)
                 .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
-                .setDeviceType(DEVICE_TYPE_SPEAKER)
+                .setDeviceType(DEVICE_TYPE_REMOTE_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);
@@ -168,9 +161,9 @@
                 .setClientPackageName("another.client.package").build();
         assertNotEquals(route, routeClient);
 
-        MediaRoute2Info routeCategory = new MediaRoute2Info.Builder(route)
-                .addSupportedCategory(CATEGORY_SPECIAL).build();
-        assertNotEquals(route, routeCategory);
+        MediaRoute2Info routeType = new MediaRoute2Info.Builder(route)
+                .addFeature(FEATURE_SPECIAL).build();
+        assertNotEquals(route, routeType);
 
         MediaRoute2Info routeVolume = new MediaRoute2Info.Builder(route)
                 .setVolume(10).build();
@@ -185,13 +178,13 @@
         assertNotEquals(route, routeVolumeHandling);
 
         MediaRoute2Info routeDeviceType = new MediaRoute2Info.Builder(route)
-                .setVolume(DEVICE_TYPE_TV).build();
+                .setVolume(DEVICE_TYPE_REMOTE_TV).build();
         assertNotEquals(route, routeDeviceType);
     }
 
     @Test
     public void testControlVolumeWithRouter() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_ALL);
 
         MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
         assertNotNull(volRoute);
@@ -202,12 +195,14 @@
         awaitOnRouteChanged(
                 () -> mRouter2.requestUpdateVolume(volRoute, deltaVolume),
                 ROUTE_ID_VARIABLE_VOLUME,
-                (route -> route.getVolume() == originalVolume + deltaVolume));
+                (route -> route.getVolume() == originalVolume + deltaVolume),
+                FEATURES_ALL);
 
         awaitOnRouteChanged(
                 () -> mRouter2.requestSetVolume(volRoute, originalVolume),
                 ROUTE_ID_VARIABLE_VOLUME,
-                (route -> route.getVolume() == originalVolume));
+                (route -> route.getVolume() == originalVolume),
+                FEATURES_ALL);
     }
 
     @Test
@@ -234,13 +229,13 @@
     @Test
     public void testRequestCreateSessionWithInvalidArguments() {
         MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build();
-        String controlCategory = "controlCategory";
+        String routeFeature = "routeFeature";
 
         // Tests null route
         assertThrows(NullPointerException.class,
-                () -> mRouter2.requestCreateSession(null, controlCategory));
+                () -> mRouter2.requestCreateSession(null, routeFeature));
 
-        // Tests null or empty control category
+        // Tests null or empty route feature
         assertThrows(IllegalArgumentException.class,
                 () -> mRouter2.requestCreateSession(route, null));
         assertThrows(IllegalArgumentException.class,
@@ -249,42 +244,42 @@
 
     @Test
     public void testRequestCreateSessionSuccess() throws Exception {
-        final List<String> sampleControlCategory = new ArrayList<>();
-        sampleControlCategory.add(CATEGORY_SAMPLE);
+        final List<String> sampleRouteFeature = new ArrayList<>();
+        sampleRouteFeature.add(FEATURE_SAMPLE);
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteFeature);
         MediaRoute2Info route = routes.get(ROUTE_ID1);
         assertNotNull(route);
 
         final CountDownLatch successLatch = new CountDownLatch(1);
         final CountDownLatch failureLatch = new CountDownLatch(1);
-        final List<RouteSessionController> controllers = new ArrayList<>();
+        final List<RoutingController> controllers = new ArrayList<>();
 
         // Create session with this route
         SessionCallback sessionCallback = new SessionCallback() {
             @Override
-            public void onSessionCreated(RouteSessionController controller) {
+            public void onSessionCreated(RoutingController controller) {
                 assertNotNull(controller);
                 assertTrue(createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1));
-                assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory()));
+                assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature()));
                 controllers.add(controller);
                 successLatch.countDown();
             }
 
             @Override
             public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
-                    String requestedControlCategory) {
+                    String requestedRouteFeature) {
                 failureLatch.countDown();
             }
         };
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
             mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(route, CATEGORY_SAMPLE);
+            mRouter2.requestCreateSession(route, FEATURE_SAMPLE);
             assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
             // onSessionCreationFailed should not be called.
@@ -298,41 +293,41 @@
 
     @Test
     public void testRequestCreateSessionFailure() throws Exception {
-        final List<String> sampleControlCategory = new ArrayList<>();
-        sampleControlCategory.add(CATEGORY_SAMPLE);
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
         MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED);
         assertNotNull(route);
 
         final CountDownLatch successLatch = new CountDownLatch(1);
         final CountDownLatch failureLatch = new CountDownLatch(1);
-        final List<RouteSessionController> controllers = new ArrayList<>();
+        final List<RoutingController> controllers = new ArrayList<>();
 
         // Create session with this route
         SessionCallback sessionCallback = new SessionCallback() {
             @Override
-            public void onSessionCreated(RouteSessionController controller) {
+            public void onSessionCreated(RoutingController controller) {
                 controllers.add(controller);
                 successLatch.countDown();
             }
 
             @Override
             public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
-                    String requestedControlCategory) {
+                    String requestedRouteFeature) {
                 assertEquals(route, requestedRoute);
-                assertTrue(TextUtils.equals(CATEGORY_SAMPLE, requestedControlCategory));
+                assertTrue(TextUtils.equals(FEATURE_SAMPLE, requestedRouteFeature));
                 failureLatch.countDown();
             }
         };
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
             mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(route, CATEGORY_SAMPLE);
+            mRouter2.requestCreateSession(route, FEATURE_SAMPLE);
             assertTrue(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
             // onSessionCreated should not be called.
@@ -346,29 +341,29 @@
 
     @Test
     public void testRequestCreateSessionMultipleSessions() throws Exception {
-        final List<String> sampleControlCategory = new ArrayList<>();
-        sampleControlCategory.add(CATEGORY_SAMPLE);
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
 
         final CountDownLatch successLatch = new CountDownLatch(2);
         final CountDownLatch failureLatch = new CountDownLatch(1);
-        final List<RouteSessionController> createdControllers = new ArrayList<>();
+        final List<RoutingController> createdControllers = new ArrayList<>();
 
         // Create session with this route
         SessionCallback sessionCallback = new SessionCallback() {
             @Override
-            public void onSessionCreated(RouteSessionController controller) {
+            public void onSessionCreated(RoutingController controller) {
                 createdControllers.add(controller);
                 successLatch.countDown();
             }
 
             @Override
             public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
-                    String requestedControlCategory) {
+                    String requestedRouteFeature) {
                 failureLatch.countDown();
             }
         };
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
         MediaRoute2Info route1 = routes.get(ROUTE_ID1);
         MediaRoute2Info route2 = routes.get(ROUTE_ID2);
         assertNotNull(route1);
@@ -376,12 +371,12 @@
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
             mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(route1, CATEGORY_SAMPLE);
-            mRouter2.requestCreateSession(route2, CATEGORY_SAMPLE);
+            mRouter2.requestCreateSession(route1, FEATURE_SAMPLE);
+            mRouter2.requestCreateSession(route2, FEATURE_SAMPLE);
             assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
             // onSessionCreationFailed should not be called.
@@ -389,14 +384,15 @@
 
             // Created controllers should have proper info
             assertEquals(2, createdControllers.size());
-            RouteSessionController controller1 = createdControllers.get(0);
-            RouteSessionController controller2 = createdControllers.get(1);
+            RoutingController controller1 = createdControllers.get(0);
+            RoutingController controller2 = createdControllers.get(1);
 
             assertNotEquals(controller1.getSessionId(), controller2.getSessionId());
             assertTrue(createRouteMap(controller1.getSelectedRoutes()).containsKey(ROUTE_ID1));
             assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(ROUTE_ID2));
-            assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller1.getControlCategory()));
-            assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller2.getControlCategory()));
+            assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller1.getRouteFeature()));
+            assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller2.getRouteFeature()));
+
         } finally {
             releaseControllers(createdControllers);
             mRouter2.unregisterRouteCallback(routeCallback);
@@ -406,39 +402,39 @@
 
     @Test
     public void testSessionCallbackIsNotCalledAfterUnregistered() throws Exception {
-        final List<String> sampleControlCategory = new ArrayList<>();
-        sampleControlCategory.add(CATEGORY_SAMPLE);
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
         MediaRoute2Info route = routes.get(ROUTE_ID1);
         assertNotNull(route);
 
         final CountDownLatch successLatch = new CountDownLatch(1);
         final CountDownLatch failureLatch = new CountDownLatch(1);
-        final List<RouteSessionController> controllers = new ArrayList<>();
+        final List<RoutingController> controllers = new ArrayList<>();
 
         // Create session with this route
         SessionCallback sessionCallback = new SessionCallback() {
             @Override
-            public void onSessionCreated(RouteSessionController controller) {
+            public void onSessionCreated(RoutingController controller) {
                 controllers.add(controller);
                 successLatch.countDown();
             }
 
             @Override
             public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
-                    String requestedControlCategory) {
+                    String requestedRouteFeature) {
                 failureLatch.countDown();
             }
         };
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
             mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(route, CATEGORY_SAMPLE);
+            mRouter2.requestCreateSession(route, FEATURE_SAMPLE);
 
             // Unregisters session callback
             mRouter2.unregisterSessionCallback(sessionCallback);
@@ -455,48 +451,49 @@
 
     // TODO: Add tests for illegal inputs if needed (e.g. selecting already selected route)
     @Test
-    public void testRouteSessionControllerSelectAndDeselectRoute() throws Exception {
-        final List<String> sampleControlCategory = new ArrayList<>();
-        sampleControlCategory.add(CATEGORY_SAMPLE);
+    public void testRoutingControllerSelectAndDeselectRoute() throws Exception {
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
         MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
         assertNotNull(routeToCreateSessionWith);
 
         final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
         final CountDownLatch onSessionInfoChangedLatchForSelect = new CountDownLatch(1);
         final CountDownLatch onSessionInfoChangedLatchForDeselect = new CountDownLatch(1);
-        final List<RouteSessionController> controllers = new ArrayList<>();
+        final List<RoutingController> controllers = new ArrayList<>();
 
         // Create session with ROUTE_ID1
         SessionCallback sessionCallback = new SessionCallback() {
             @Override
-            public void onSessionCreated(RouteSessionController controller) {
+            public void onSessionCreated(RoutingController controller) {
                 assertNotNull(controller);
                 assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
-                assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory()));
+                assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature()));
                 controllers.add(controller);
                 onSessionCreatedLatch.countDown();
             }
 
             @Override
-            public void onSessionInfoChanged(RouteSessionController controller,
-                    RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+            public void onSessionInfoChanged(RoutingController controller,
+                    RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) {
                 if (onSessionCreatedLatch.getCount() != 0
-                        || controllers.get(0).getSessionId() != controller.getSessionId()) {
+                        || !TextUtils.equals(
+                                controllers.get(0).getSessionId(), controller.getSessionId())) {
                     return;
                 }
 
                 if (onSessionInfoChangedLatchForSelect.getCount() != 0) {
                     // Check oldInfo
-                    assertEquals(controller.getSessionId(), oldInfo.getSessionId());
+                    assertEquals(controller.getSessionId(), oldInfo.getId());
                     assertEquals(1, oldInfo.getSelectedRoutes().size());
                     assertTrue(oldInfo.getSelectedRoutes().contains(ROUTE_ID1));
                     assertTrue(oldInfo.getSelectableRoutes().contains(
                             ROUTE_ID4_TO_SELECT_AND_DESELECT));
 
                     // Check newInfo
-                    assertEquals(controller.getSessionId(), newInfo.getSessionId());
+                    assertEquals(controller.getSessionId(), newInfo.getId());
                     assertEquals(2, newInfo.getSelectedRoutes().size());
                     assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID1));
                     assertTrue(newInfo.getSelectedRoutes().contains(
@@ -507,7 +504,7 @@
                     onSessionInfoChangedLatchForSelect.countDown();
                 } else {
                     // Check newInfo
-                    assertEquals(controller.getSessionId(), newInfo.getSessionId());
+                    assertEquals(controller.getSessionId(), newInfo.getId());
                     assertEquals(1, newInfo.getSelectedRoutes().size());
                     assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID1));
                     assertFalse(newInfo.getSelectedRoutes().contains(
@@ -522,15 +519,15 @@
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
             mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(routeToCreateSessionWith, CATEGORY_SAMPLE);
+            mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE);
             assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
             assertEquals(1, controllers.size());
-            RouteSessionController controller = controllers.get(0);
+            RoutingController controller = controllers.get(0);
             assertTrue(getRouteIds(controller.getSelectableRoutes())
                     .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
 
@@ -553,45 +550,46 @@
     }
 
     @Test
-    public void testRouteSessionControllerTransferToRoute() throws Exception {
-        final List<String> sampleControlCategory = new ArrayList<>();
-        sampleControlCategory.add(CATEGORY_SAMPLE);
+    public void testRoutingControllerTransferToRoute() throws Exception {
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
         MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
         assertNotNull(routeToCreateSessionWith);
 
         final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
         final CountDownLatch onSessionInfoChangedLatch = new CountDownLatch(1);
-        final List<RouteSessionController> controllers = new ArrayList<>();
+        final List<RoutingController> controllers = new ArrayList<>();
 
         // Create session with ROUTE_ID1
         SessionCallback sessionCallback = new SessionCallback() {
             @Override
-            public void onSessionCreated(RouteSessionController controller) {
+            public void onSessionCreated(RoutingController controller) {
                 assertNotNull(controller);
                 assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
-                assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory()));
+                assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature()));
                 controllers.add(controller);
                 onSessionCreatedLatch.countDown();
             }
 
             @Override
-            public void onSessionInfoChanged(RouteSessionController controller,
-                    RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+            public void onSessionInfoChanged(RoutingController controller,
+                    RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) {
                 if (onSessionCreatedLatch.getCount() != 0
-                        || controllers.get(0).getSessionId() != controller.getSessionId()) {
+                        || !TextUtils.equals(
+                                controllers.get(0).getSessionId(), controller.getSessionId())) {
                     return;
                 }
 
                 // Check oldInfo
-                assertEquals(controller.getSessionId(), oldInfo.getSessionId());
+                assertEquals(controller.getSessionId(), oldInfo.getId());
                 assertEquals(1, oldInfo.getSelectedRoutes().size());
                 assertTrue(oldInfo.getSelectedRoutes().contains(ROUTE_ID1));
                 assertTrue(oldInfo.getTransferrableRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO));
 
                 // Check newInfo
-                assertEquals(controller.getSessionId(), newInfo.getSessionId());
+                assertEquals(controller.getSessionId(), newInfo.getId());
                 assertEquals(1, newInfo.getSelectedRoutes().size());
                 assertFalse(newInfo.getSelectedRoutes().contains(ROUTE_ID1));
                 assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO));
@@ -603,15 +601,15 @@
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
             mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(routeToCreateSessionWith, CATEGORY_SAMPLE);
+            mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE);
             assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
             assertEquals(1, controllers.size());
-            RouteSessionController controller = controllers.get(0);
+            RoutingController controller = controllers.get(0);
             assertTrue(getRouteIds(controller.getTransferrableRoutes())
                     .contains(ROUTE_ID5_TO_TRANSFER_TO));
 
@@ -631,34 +629,35 @@
     // TODO: Add tests for onSessionReleased() call.
 
     @Test
-    public void testRouteSessionControllerReleaseShouldIgnoreTransferTo() throws Exception {
-        final List<String> sampleControlCategory = new ArrayList<>();
-        sampleControlCategory.add(CATEGORY_SAMPLE);
+    public void testRoutingControllerReleaseShouldIgnoreTransferTo() throws Exception {
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
         MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
         assertNotNull(routeToCreateSessionWith);
 
         final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
         final CountDownLatch onSessionInfoChangedLatch = new CountDownLatch(1);
-        final List<RouteSessionController> controllers = new ArrayList<>();
+        final List<RoutingController> controllers = new ArrayList<>();
 
         // Create session with ROUTE_ID1
         SessionCallback sessionCallback = new SessionCallback() {
             @Override
-            public void onSessionCreated(RouteSessionController controller) {
+            public void onSessionCreated(RoutingController controller) {
                 assertNotNull(controller);
                 assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
-                assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory()));
+                assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature()));
                 controllers.add(controller);
                 onSessionCreatedLatch.countDown();
             }
 
             @Override
-            public void onSessionInfoChanged(RouteSessionController controller,
-                    RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+            public void onSessionInfoChanged(RoutingController controller,
+                    RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) {
                 if (onSessionCreatedLatch.getCount() != 0
-                        || controllers.get(0).getSessionId() != controller.getSessionId()) {
+                        || !TextUtils.equals(
+                                controllers.get(0).getSessionId(), controller.getSessionId())) {
                     return;
                 }
                 onSessionInfoChangedLatch.countDown();
@@ -667,15 +666,15 @@
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
             mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(routeToCreateSessionWith, CATEGORY_SAMPLE);
+            mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE);
             assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
             assertEquals(1, controllers.size());
-            RouteSessionController controller = controllers.get(0);
+            RoutingController controller = controllers.get(0);
             assertTrue(getRouteIds(controller.getTransferrableRoutes())
                     .contains(ROUTE_ID5_TO_TRANSFER_TO));
 
@@ -701,17 +700,16 @@
     static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
         Map<String, MediaRoute2Info> routeMap = new HashMap<>();
         for (MediaRoute2Info route : routes) {
-            // intentionally not using route.getUniqueId() for convenience.
             routeMap.put(route.getId(), route);
         }
         return routeMap;
     }
 
-    Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> controlCategories)
+    Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> routeTypes)
             throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
 
-        // A dummy callback is required to send control category info.
+        // A dummy callback is required to send route type info.
         RouteCallback routeCallback = new RouteCallback() {
             @Override
             public void onRoutesAdded(List<MediaRoute2Info> routes) {
@@ -724,8 +722,8 @@
             }
         };
 
-        mRouter2.setControlCategories(controlCategories);
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback,
+                new RouteDiscoveryPreference.Builder(routeTypes, true).build());
         try {
             latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
             return createRouteMap(mRouter2.getRoutes());
@@ -734,14 +732,14 @@
         }
     }
 
-    static void releaseControllers(@NonNull List<RouteSessionController> controllers) {
-        for (RouteSessionController controller : controllers) {
+    static void releaseControllers(@NonNull List<RoutingController> controllers) {
+        for (RoutingController controller : controllers) {
             controller.release();
         }
     }
 
     /**
-     * Returns a list of IDs (not uniqueId) of the given route list.
+     * Returns a list of IDs of the given route list.
      */
     List<String> getRouteIds(@NonNull List<MediaRoute2Info> routes) {
         List<String> result = new ArrayList<>();
@@ -752,7 +750,8 @@
     }
 
     void awaitOnRouteChanged(Runnable task, String routeId,
-            Predicate<MediaRoute2Info> predicate) throws Exception {
+            Predicate<MediaRoute2Info> predicate,
+            List<String> routeTypes) throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         RouteCallback routeCallback = new RouteCallback() {
             @Override
@@ -763,7 +762,8 @@
                 }
             }
         };
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback,
+                new RouteDiscoveryPreference.Builder(routeTypes, true).build());
         try {
             task.run();
             assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index 83c7c17..ae15b91 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -31,6 +31,7 @@
 import android.media.MediaRouter2.RouteCallback;
 import android.media.MediaRouter2.SessionCallback;
 import android.media.MediaRouter2Manager;
+import android.media.RouteDiscoveryPreference;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -57,41 +58,48 @@
 public class MediaRouterManagerTest {
     private static final String TAG = "MediaRouterManagerTest";
 
-    // Must be the same as SampleMediaRoute2ProviderService
-    public static final String ROUTE_ID1 = "route_id1";
+    public static final String SAMPLE_PROVIDER_ROUTES_ID_PREFIX =
+            "com.android.mediarouteprovider.example/.SampleMediaRoute2ProviderService:";
+
+    // Must be the same as SampleMediaRoute2ProviderService except the prefix of IDs.
+    public static final String ROUTE_ID1 = SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id1";
     public static final String ROUTE_NAME1 = "Sample Route 1";
-    public static final String ROUTE_ID2 = "route_id2";
+    public static final String ROUTE_ID2 = SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "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";
+            SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id3_session_creation_failed";
     public static final String ROUTE_NAME3 = "Sample Route 3 - Session creation failed";
     public static final String ROUTE_ID4_TO_SELECT_AND_DESELECT =
-            "route_id4_to_select_and_deselect";
+            SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id4_to_select_and_deselect";
     public static final String ROUTE_NAME4 = "Sample Route 4 - Route to select and deselect";
-    public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to";
+    public static final String ROUTE_ID5_TO_TRANSFER_TO =
+            SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id5_to_transfer_to";
     public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to";
 
-    public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
-    public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
+    public static final String ROUTE_ID_SPECIAL_FEATURE =
+            SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_special_feature";
+    public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route";
 
     public static final String SYSTEM_PROVIDER_ID =
             "com.android.server.media/.SystemMediaRoute2Provider";
 
     public static final int VOLUME_MAX = 100;
-    public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume";
+    public static final String ROUTE_ID_FIXED_VOLUME =
+            SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_fixed_volume";
     public static final String ROUTE_NAME_FIXED_VOLUME = "Fixed Volume Route";
-    public static final String ROUTE_ID_VARIABLE_VOLUME = "route_variable_volume";
+    public static final String ROUTE_ID_VARIABLE_VOLUME =
+            SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_variable_volume";
     public static final String ROUTE_NAME_VARIABLE_VOLUME = "Variable Volume Route";
 
     public static final String ACTION_REMOVE_ROUTE =
             "com.android.mediarouteprovider.action_remove_route";
 
-    public static final String CATEGORY_SAMPLE =
-            "com.android.mediarouteprovider.CATEGORY_SAMPLE";
-    public static final String CATEGORY_SPECIAL =
-            "com.android.mediarouteprovider.CATEGORY_SPECIAL";
+    public static final String FEATURE_SAMPLE =
+            "com.android.mediarouteprovider.FEATURE_SAMPLE";
+    public static final String FEATURE_SPECIAL =
+            "com.android.mediarouteprovider.FEATURE_SPECIAL";
 
-    private static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
+    private static final String FEATURE_LIVE_AUDIO = "android.media.intent.route.LIVE_AUDIO";
 
     private static final int TIMEOUT_MS = 5000;
 
@@ -105,18 +113,18 @@
     private final List<RouteCallback> mRouteCallbacks = new ArrayList<>();
     private final List<SessionCallback> mSessionCallbacks = new ArrayList<>();
 
-    public static final List<String> CATEGORIES_ALL = new ArrayList();
-    public static final List<String> CATEGORIES_SPECIAL = new ArrayList();
-    private static final List<String> CATEGORIES_LIVE_AUDIO = new ArrayList<>();
+    public static final List<String> FEATURES_ALL = new ArrayList();
+    public static final List<String> FEATURES_SPECIAL = new ArrayList();
+    private static final List<String> FEATURES_LIVE_AUDIO = new ArrayList<>();
 
     static {
-        CATEGORIES_ALL.add(CATEGORY_SAMPLE);
-        CATEGORIES_ALL.add(CATEGORY_SPECIAL);
-        CATEGORIES_ALL.add(CATEGORY_LIVE_AUDIO);
+        FEATURES_ALL.add(FEATURE_SAMPLE);
+        FEATURES_ALL.add(FEATURE_SPECIAL);
+        FEATURES_ALL.add(FEATURE_LIVE_AUDIO);
 
-        CATEGORIES_SPECIAL.add(CATEGORY_SPECIAL);
+        FEATURES_SPECIAL.add(FEATURE_SPECIAL);
 
-        CATEGORIES_LIVE_AUDIO.add(CATEGORY_LIVE_AUDIO);
+        FEATURES_LIVE_AUDIO.add(FEATURE_LIVE_AUDIO);
     }
 
     @Before
@@ -173,7 +181,7 @@
     @Test
     public void testOnRoutesRemoved() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
 
         addRouterCallback(new RouteCallback());
         addManagerCallback(new MediaRouter2Manager.Callback() {
@@ -195,14 +203,14 @@
     }
 
     /**
-     * Tests if we get proper routes for application that has special control category.
+     * Tests if we get proper routes for application that has special route feature.
      */
     @Test
-    public void testControlCategory() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_SPECIAL);
+    public void testRouteFeatures() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_SPECIAL);
 
         assertEquals(1, routes.size());
-        assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
+        assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
     }
 
     /**
@@ -211,7 +219,7 @@
      */
     @Test
     public void testRouterOnSessionCreated() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
 
         CountDownLatch latch = new CountDownLatch(1);
 
@@ -220,7 +228,7 @@
         addRouterCallback(new MediaRouter2.RouteCallback());
         addSessionCallback(new SessionCallback() {
             @Override
-            public void onSessionCreated(MediaRouter2.RouteSessionController controller) {
+            public void onSessionCreated(MediaRouter2.RoutingController controller) {
                 if (createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1)) {
                     latch.countDown();
                 }
@@ -247,7 +255,7 @@
     @Ignore("TODO: test session created callback instead of onRouteSelected")
     public void testManagerOnRouteSelected() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
 
         addRouterCallback(new RouteCallback());
         addManagerCallback(new MediaRouter2Manager.Callback() {
@@ -277,7 +285,7 @@
     public void testGetActiveRoutes() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
         addRouterCallback(new RouteCallback());
         addManagerCallback(new MediaRouter2Manager.Callback() {
             @Override
@@ -313,7 +321,7 @@
     @Test
     @Ignore("TODO: enable when session is released")
     public void testSingleProviderSelect() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
         addRouterCallback(new RouteCallback());
 
         awaitOnRouteChangedManager(
@@ -338,7 +346,7 @@
 
     @Test
     public void testControlVolumeWithManager() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
         MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
 
         int originalVolume = volRoute.getVolume();
@@ -357,7 +365,7 @@
 
     @Test
     public void testVolumeHandling() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
 
         MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME);
         MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
@@ -367,11 +375,11 @@
         assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax());
     }
 
-    Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> controlCategories)
+    Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeFeatures)
             throws Exception {
         CountDownLatch latch = new CountDownLatch(2);
 
-        // A dummy callback is required to send control category info.
+        // A dummy callback is required to send route feature info.
         RouteCallback routeCallback = new RouteCallback();
         MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
             @Override
@@ -386,16 +394,17 @@
             }
 
             @Override
-            public void onControlCategoriesChanged(String packageName, List<String> categories) {
+            public void onControlCategoriesChanged(String packageName,
+                    List<String> preferredFeatures) {
                 if (TextUtils.equals(mPackageName, packageName)
-                        && controlCategories.equals(categories)) {
+                        && preferredFeatures.equals(preferredFeatures)) {
                     latch.countDown();
                 }
             }
         };
         mManager.registerCallback(mExecutor, managerCallback);
-        mRouter2.setControlCategories(controlCategories);
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback,
+                new RouteDiscoveryPreference.Builder(routeFeatures, true).build());
         try {
             latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
             return createRouteMap(mManager.getAvailableRoutes(mPackageName));
@@ -430,7 +439,6 @@
     static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
         Map<String, MediaRoute2Info> routeMap = new HashMap<>();
         for (MediaRoute2Info route : routes) {
-            // intentionally not using route.getUniqueId() for convenience.
             routeMap.put(route.getId(), route);
         }
         return routeMap;
@@ -443,7 +451,7 @@
 
     private void addRouterCallback(RouteCallback routeCallback) {
         mRouteCallbacks.add(routeCallback);
-        mRouter2.registerRouteCallback(mExecutor, routeCallback);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
     }
 
     private void addSessionCallback(SessionCallback sessionCallback) {
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java
new file mode 100644
index 0000000..fa12935
--- /dev/null
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mediaroutertest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.media.RouteDiscoveryPreference;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RouteDiscoveryPreferenceTest {
+    @Before
+    public void setUp() throws Exception { }
+
+    @After
+    public void tearDown() throws Exception { }
+
+    @Test
+    public void testEquality() {
+        List<String> testTypes = new ArrayList<>();
+        testTypes.add("TEST_TYPE_1");
+        testTypes.add("TEST_TYPE_2");
+        RouteDiscoveryPreference request = new RouteDiscoveryPreference.Builder(testTypes, true)
+                .build();
+
+        RouteDiscoveryPreference requestRebuilt = new RouteDiscoveryPreference.Builder(request)
+                .build();
+
+        assertEquals(request, requestRebuilt);
+
+        Parcel parcel = Parcel.obtain();
+        parcel.writeParcelable(request, 0);
+        parcel.setDataPosition(0);
+        RouteDiscoveryPreference requestFromParcel = parcel.readParcelable(null);
+
+        assertEquals(request, requestFromParcel);
+    }
+
+    @Test
+    public void testInequality() {
+        List<String> testTypes = new ArrayList<>();
+        testTypes.add("TEST_TYPE_1");
+        testTypes.add("TEST_TYPE_2");
+
+        List<String> testTypes2 = new ArrayList<>();
+        testTypes.add("TEST_TYPE_3");
+
+        RouteDiscoveryPreference request = new RouteDiscoveryPreference.Builder(testTypes, true)
+                .build();
+
+        RouteDiscoveryPreference requestTypes = new RouteDiscoveryPreference.Builder(request)
+                .setPreferredFeatures(testTypes2)
+                .build();
+        assertNotEquals(request, requestTypes);
+
+        RouteDiscoveryPreference requestActiveScan = new RouteDiscoveryPreference.Builder(request)
+                .setActiveScan(false)
+                .build();
+        assertNotEquals(request, requestActiveScan);
+    }
+}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java
deleted file mode 100644
index 2e81a64..0000000
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.mediaroutertest;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.media.RouteSessionInfo;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class RouteSessionTest {
-    private static final String TEST_PACKAGE_NAME = "com.android.mediaroutertest";
-    private static final String TEST_CONTROL_CATEGORY = "com.android.mediaroutertest.category";
-
-    private static final String TEST_ROUTE_ID1 = "route_id1";
-
-    @Test
-    public void testValidity() {
-        RouteSessionInfo emptyPackageSession = new RouteSessionInfo.Builder(1,
-                "",
-                TEST_CONTROL_CATEGORY)
-                .addSelectedRoute(TEST_ROUTE_ID1)
-                .build();
-        RouteSessionInfo emptyCategorySession = new RouteSessionInfo.Builder(1,
-                TEST_PACKAGE_NAME, "")
-                .addSelectedRoute(TEST_ROUTE_ID1)
-                .build();
-
-        RouteSessionInfo emptySelectedRouteSession = new RouteSessionInfo.Builder(1,
-                TEST_PACKAGE_NAME, TEST_CONTROL_CATEGORY)
-                .build();
-
-        RouteSessionInfo validSession = new RouteSessionInfo.Builder(emptySelectedRouteSession)
-                .addSelectedRoute(TEST_ROUTE_ID1)
-                .build();
-
-        assertFalse(emptySelectedRouteSession.isValid());
-        assertFalse(emptyPackageSession.isValid());
-        assertFalse(emptyCategorySession.isValid());
-        assertTrue(validSession.isValid());
-    }
-}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java
new file mode 100644
index 0000000..3f59736
--- /dev/null
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java
@@ -0,0 +1,560 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mediaroutertest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.media.RoutingSessionInfo;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests {@link RoutingSessionInfo} and its {@link RoutingSessionInfo.Builder builder}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RoutingSessionInfoTest {
+    public static final String TEST_ID = "test_id";
+    public static final String TEST_CLIENT_PACKAGE_NAME = "com.test.client.package.name";
+    public static final String TEST_ROUTE_FEATURE = "test_route_feature";
+
+    public static final String TEST_ROUTE_ID_0 = "test_route_type_0";
+    public static final String TEST_ROUTE_ID_1 = "test_route_type_1";
+    public static final String TEST_ROUTE_ID_2 = "test_route_type_2";
+    public static final String TEST_ROUTE_ID_3 = "test_route_type_3";
+    public static final String TEST_ROUTE_ID_4 = "test_route_type_4";
+    public static final String TEST_ROUTE_ID_5 = "test_route_type_5";
+    public static final String TEST_ROUTE_ID_6 = "test_route_type_6";
+    public static final String TEST_ROUTE_ID_7 = "test_route_type_7";
+
+    public static final String TEST_KEY = "test_key";
+    public static final String TEST_VALUE = "test_value";
+
+    @Test
+    public void testBuilderConstructorWithInvalidValues() {
+        final String nullId = null;
+        final String nullClientPackageName = null;
+        final String nullRouteFeature = null;
+
+        final String emptyId = "";
+        // Note: An empty string as client package name is valid.
+        final String emptyRouteFeature = "";
+
+        final String validId = TEST_ID;
+        final String validClientPackageName = TEST_CLIENT_PACKAGE_NAME;
+        final String validRouteFeature = TEST_ROUTE_FEATURE;
+
+        // ID is invalid
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                nullId, validClientPackageName, validRouteFeature));
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                emptyId, validClientPackageName, validRouteFeature));
+
+        // client package name is invalid (null)
+        assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder(
+                validId, nullClientPackageName, validRouteFeature));
+
+        // route feature is invalid
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                validId, validClientPackageName, nullRouteFeature));
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                validId, validClientPackageName, emptyRouteFeature));
+
+        // Two arguments are invalid - (1) ID and clientPackageName
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                nullId, nullClientPackageName, validRouteFeature));
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                emptyId, nullClientPackageName, validRouteFeature));
+
+        // Two arguments are invalid - (2) ID and routeFeature
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                nullId, validClientPackageName, nullRouteFeature));
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                nullId, validClientPackageName, emptyRouteFeature));
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                emptyId, validClientPackageName, nullRouteFeature));
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                emptyId, validClientPackageName, emptyRouteFeature));
+
+        // Two arguments are invalid - (3) clientPackageName and routeFeature
+        // Note that this throws NullPointerException.
+        assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder(
+                validId, nullClientPackageName, nullRouteFeature));
+        assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder(
+                validId, nullClientPackageName, emptyRouteFeature));
+
+        // All arguments are invalid
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                nullId, nullClientPackageName, nullRouteFeature));
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                nullId, nullClientPackageName, emptyRouteFeature));
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                emptyId, nullClientPackageName, nullRouteFeature));
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                emptyId, nullClientPackageName, emptyRouteFeature));
+
+        // Null RouteInfo (1-argument constructor)
+        final RoutingSessionInfo nullRoutingSessionInfo = null;
+        assertThrows(NullPointerException.class,
+                () -> new RoutingSessionInfo.Builder(nullRoutingSessionInfo));
+    }
+
+    @Test
+    public void testBuilderConstructorWithEmptyClientPackageName() {
+        // An empty string for client package name is valid. (for unknown cases)
+        // Creating builder with it should not throw any exception.
+        RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+                TEST_ID, "" /* clientPackageName*/, TEST_ROUTE_FEATURE);
+    }
+
+    @Test
+    public void testBuilderBuildWithEmptySelectedRoutesThrowsIAE() {
+        RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE);
+        // Note: Calling build() without adding any selected routes.
+        assertThrows(IllegalArgumentException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testBuilderAddRouteMethodsWithIllegalArgumentsThrowsIAE() {
+        RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE);
+
+        final String nullRouteId = null;
+        final String emptyRouteId = "";
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSelectedRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSelectableRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addDeselectableRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addTransferrableRoute(nullRouteId));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSelectedRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSelectableRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addDeselectableRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addTransferrableRoute(emptyRouteId));
+    }
+
+    @Test
+    public void testBuilderRemoveRouteMethodsWithIllegalArgumentsThrowsIAE() {
+        RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE);
+
+        final String nullRouteId = null;
+        final String emptyRouteId = "";
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeSelectedRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeSelectableRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeDeselectableRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeTransferrableRoute(nullRouteId));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeSelectedRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeSelectableRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeDeselectableRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeTransferrableRoute(emptyRouteId));
+    }
+
+    @Test
+    public void testBuilderAndGettersOfRoutingSessionInfo() {
+        Bundle controlHints = new Bundle();
+        controlHints.putString(TEST_KEY, TEST_VALUE);
+
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .setControlHints(controlHints)
+                .build();
+
+        assertEquals(TEST_ID, sessionInfo.getId());
+        assertEquals(TEST_CLIENT_PACKAGE_NAME, sessionInfo.getClientPackageName());
+        assertEquals(TEST_ROUTE_FEATURE, sessionInfo.getRouteFeature());
+
+        assertEquals(2, sessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_1, sessionInfo.getSelectedRoutes().get(1));
+
+        assertEquals(2, sessionInfo.getSelectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_3, sessionInfo.getSelectableRoutes().get(1));
+
+        assertEquals(2, sessionInfo.getDeselectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_5, sessionInfo.getDeselectableRoutes().get(1));
+
+        assertEquals(2, sessionInfo.getTransferrableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferrableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_7, sessionInfo.getTransferrableRoutes().get(1));
+
+        Bundle controlHintsOut = sessionInfo.getControlHints();
+        assertNotNull(controlHintsOut);
+        assertTrue(controlHintsOut.containsKey(TEST_KEY));
+        assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY));
+    }
+
+    @Test
+    public void testBuilderAddRouteMethodsWithBuilderCopyConstructor() {
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .build();
+
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .build();
+
+        assertEquals(2, newSessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_1, newSessionInfo.getSelectedRoutes().get(1));
+
+        assertEquals(2, newSessionInfo.getSelectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_3, newSessionInfo.getSelectableRoutes().get(1));
+
+        assertEquals(2, newSessionInfo.getDeselectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_5, newSessionInfo.getDeselectableRoutes().get(1));
+
+        assertEquals(2, newSessionInfo.getTransferrableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferrableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_7, newSessionInfo.getTransferrableRoutes().get(1));
+    }
+
+    @Test
+    public void testBuilderRemoveRouteMethods() {
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .removeSelectedRoute(TEST_ROUTE_ID_1)
+
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .removeSelectableRoute(TEST_ROUTE_ID_3)
+
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .removeDeselectableRoute(TEST_ROUTE_ID_5)
+
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .removeTransferrableRoute(TEST_ROUTE_ID_7)
+
+                .build();
+
+        assertEquals(1, sessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+
+        assertEquals(1, sessionInfo.getSelectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0));
+
+        assertEquals(1, sessionInfo.getDeselectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0));
+
+        assertEquals(1, sessionInfo.getTransferrableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferrableRoutes().get(0));
+    }
+
+    @Test
+    public void testBuilderRemoveRouteMethodsWithBuilderCopyConstructor() {
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .build();
+
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+                .removeSelectedRoute(TEST_ROUTE_ID_1)
+                .removeSelectableRoute(TEST_ROUTE_ID_3)
+                .removeDeselectableRoute(TEST_ROUTE_ID_5)
+                .removeTransferrableRoute(TEST_ROUTE_ID_7)
+                .build();
+
+        assertEquals(1, newSessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+
+        assertEquals(1, newSessionInfo.getSelectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0));
+
+        assertEquals(1, newSessionInfo.getDeselectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0));
+
+        assertEquals(1, newSessionInfo.getTransferrableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferrableRoutes().get(0));
+    }
+
+    @Test
+    public void testBuilderClearRouteMethods() {
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .clearSelectedRoutes()
+
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .clearSelectableRoutes()
+
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .clearDeselectableRoutes()
+
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .clearTransferrableRoutes()
+
+                // SelectedRoutes must not be empty
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .build();
+
+        assertEquals(1, sessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+
+        assertTrue(sessionInfo.getSelectableRoutes().isEmpty());
+        assertTrue(sessionInfo.getDeselectableRoutes().isEmpty());
+        assertTrue(sessionInfo.getTransferrableRoutes().isEmpty());
+    }
+
+    @Test
+    public void testBuilderClearRouteMethodsWithBuilderCopyConstructor() {
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .build();
+
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+                .clearSelectedRoutes()
+                .clearSelectableRoutes()
+                .clearDeselectableRoutes()
+                .clearTransferrableRoutes()
+                // SelectedRoutes must not be empty
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .build();
+
+        assertEquals(1, newSessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+
+        assertTrue(newSessionInfo.getSelectableRoutes().isEmpty());
+        assertTrue(newSessionInfo.getDeselectableRoutes().isEmpty());
+        assertTrue(newSessionInfo.getTransferrableRoutes().isEmpty());
+    }
+
+    @Test
+    public void testEqualsCreatedWithSameArguments() {
+        Bundle controlHints = new Bundle();
+        controlHints.putString(TEST_KEY, TEST_VALUE);
+
+        RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .setControlHints(controlHints)
+                .build();
+
+        RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .setControlHints(controlHints)
+                .build();
+
+        assertEquals(sessionInfo1, sessionInfo2);
+        assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode());
+    }
+
+    @Test
+    public void testEqualsCreatedWithBuilderCopyConstructor() {
+        Bundle controlHints = new Bundle();
+        controlHints.putString(TEST_KEY, TEST_VALUE);
+
+        RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .setControlHints(controlHints)
+                .build();
+
+        RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(sessionInfo1).build();
+
+        assertEquals(sessionInfo1, sessionInfo2);
+        assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode());
+    }
+
+    @Test
+    public void testEqualsReturnFalse() {
+        Bundle controlHints = new Bundle();
+        controlHints.putString(TEST_KEY, TEST_VALUE);
+
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .setControlHints(controlHints)
+                .build();
+
+        // Now, we will use copy constructor
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .addSelectedRoute("randomRoute")
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .addSelectableRoute("randomRoute")
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .addDeselectableRoute("randomRoute")
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .addTransferrableRoute("randomRoute")
+                .build());
+
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .removeSelectedRoute(TEST_ROUTE_ID_1)
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .removeSelectableRoute(TEST_ROUTE_ID_3)
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .removeDeselectableRoute(TEST_ROUTE_ID_5)
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .removeTransferrableRoute(TEST_ROUTE_ID_7)
+                .build());
+
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .clearSelectedRoutes()
+                // Note: Calling build() with empty selected routes will throw IAE.
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .clearSelectableRoutes()
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .clearDeselectableRoutes()
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .clearTransferrableRoutes()
+                .build());
+
+        // Note: ControlHints will not affect the equals.
+    }
+
+    @Test
+    public void testParcelingAndUnParceling() {
+        Bundle controlHints = new Bundle();
+        controlHints.putString(TEST_KEY, TEST_VALUE);
+
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferrableRoute(TEST_ROUTE_ID_6)
+                .addTransferrableRoute(TEST_ROUTE_ID_7)
+                .setControlHints(controlHints)
+                .build();
+
+        Parcel parcel = Parcel.obtain();
+        sessionInfo.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        RoutingSessionInfo sessionInfoFromParcel =
+                RoutingSessionInfo.CREATOR.createFromParcel(parcel);
+        assertEquals(sessionInfo, sessionInfoFromParcel);
+        assertEquals(sessionInfo.hashCode(), sessionInfoFromParcel.hashCode());
+
+        // Check controlHints
+        Bundle controlHintsOut = sessionInfoFromParcel.getControlHints();
+        assertNotNull(controlHintsOut);
+        assertTrue(controlHintsOut.containsKey(TEST_KEY));
+        assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY));
+    }
+}
diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types
index cb04d92..c1f8b3f 100644
--- a/mime/java-res/android.mime.types
+++ b/mime/java-res/android.mime.types
@@ -46,6 +46,7 @@
 # <extension1> and <extension2> are mapped (or remapped) to <mimeType>.
 
 ?application/epub+zip epub
+?application/lrc lrc
 ?application/pkix-cert cer
 ?application/rss+xml rss
 ?application/sdp sdp
@@ -65,6 +66,7 @@
 ?application/x-mpegurl m3u m3u8
 ?application/x-pem-file pem
 ?application/x-pkcs12 p12 pfx
+?application/x-subrip srt
 ?application/x-webarchive webarchive
 ?application/x-webarchive-xml webarchivexml
 ?application/x-x509-server-cert crt
diff --git a/mms/OWNERS b/mms/OWNERS
index ba00d5d..befc320 100644
--- a/mms/OWNERS
+++ b/mms/OWNERS
@@ -12,3 +12,5 @@
 shuoq@google.com
 refuhoo@google.com
 nazaninb@google.com
+sarahchin@google.com
+dbright@google.com
\ No newline at end of file
diff --git a/mms/java/android/telephony/MmsManager.java b/mms/java/android/telephony/MmsManager.java
index 6554267..cf55eba 100644
--- a/mms/java/android/telephony/MmsManager.java
+++ b/mms/java/android/telephony/MmsManager.java
@@ -16,8 +16,12 @@
 
 package android.telephony;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
 import android.app.ActivityThread;
 import android.app.PendingIntent;
+import android.content.Context;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -27,22 +31,18 @@
 
 /**
  * Manages MMS operations such as sending multimedia messages.
- * Get this object by calling the static method {@link #getInstance()}.
- * @hide
+ * Get this object by calling Context#getSystemService(Context#MMS_SERVICE).
  */
+@SystemService(Context.MMS_SERVICE)
 public class MmsManager {
     private static final String TAG = "MmsManager";
-
-    /** Singleton object constructed during class initialization. */
-    private static final MmsManager sInstance = new MmsManager();
+    private final Context mContext;
 
     /**
-     * Get the MmsManager singleton instance.
-     *
-     * @return the {@link MmsManager} singleton instance.
+     * @hide
      */
-    public static MmsManager getInstance() {
-        return sInstance;
+    public MmsManager(@NonNull Context context) {
+        mContext = context;
     }
 
     /**
@@ -56,8 +56,9 @@
      * @param sentIntent if not NULL this <code>PendingIntent</code> is broadcast when the message
      *                   is successfully sent, or failed
      */
-    public void sendMultimediaMessage(int subId, Uri contentUri, String locationUrl,
-            Bundle configOverrides, PendingIntent sentIntent) {
+    public void sendMultimediaMessage(int subId, @NonNull Uri contentUri,
+            @Nullable String locationUrl, @Nullable Bundle configOverrides,
+            @Nullable PendingIntent sentIntent) {
         try {
             final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
             if (iMms == null) {
@@ -84,8 +85,9 @@
      *  broadcast when the message is downloaded, or the download is failed
      * @throws IllegalArgumentException if locationUrl or contentUri is empty
      */
-    public void downloadMultimediaMessage(int subId, String locationUrl, Uri contentUri,
-            Bundle configOverrides, PendingIntent downloadedIntent) {
+    public void downloadMultimediaMessage(int subId, @NonNull String locationUrl,
+            @NonNull Uri contentUri, @Nullable Bundle configOverrides,
+            @Nullable PendingIntent downloadedIntent) {
         try {
             final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
             if (iMms == null) {
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 942eafd..376ea77 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -24,12 +24,21 @@
 
     // our source files
     //
-    srcs: ["bitmap.cpp"],
+    srcs: [
+        "aassetstreamadaptor.cpp",
+        "bitmap.cpp",
+        "imagedecoder.cpp",
+    ],
 
     shared_libs: [
+        "libandroid",
         "libandroid_runtime",
+        "libhwui",
+        "liblog",
     ],
 
+    static_libs: ["libarect"],
+
     arch: {
         arm: {
             // TODO: This is to work around b/24465209. Remove after root cause is fixed
diff --git a/native/graphics/jni/aassetstreamadaptor.cpp b/native/graphics/jni/aassetstreamadaptor.cpp
new file mode 100644
index 0000000..016008b
--- /dev/null
+++ b/native/graphics/jni/aassetstreamadaptor.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "aassetstreamadaptor.h"
+
+#include <log/log.h>
+
+AAssetStreamAdaptor::AAssetStreamAdaptor(AAsset* asset)
+    : mAAsset(asset)
+{
+}
+
+bool AAssetStreamAdaptor::rewind() {
+    off64_t pos = AAsset_seek64(mAAsset, 0, SEEK_SET);
+    if (pos == (off64_t)-1) {
+        ALOGE("rewind failed!");
+        return false;
+    }
+    return true;
+}
+
+size_t AAssetStreamAdaptor::getLength() const {
+    return AAsset_getLength64(mAAsset);
+}
+
+bool AAssetStreamAdaptor::isAtEnd() const {
+    return AAsset_getRemainingLength64(mAAsset) == 0;
+}
+
+SkStreamRewindable* AAssetStreamAdaptor::onDuplicate() const {
+    // Cannot sensibly create a duplicate, since each AAssetStreamAdaptor
+    // would be modifying the same AAsset.
+    //return new AAssetStreamAdaptor(mAAsset);
+    return nullptr;
+}
+
+bool AAssetStreamAdaptor::hasPosition() const {
+    return AAsset_seek64(mAAsset, 0, SEEK_CUR) != -1;
+}
+
+size_t AAssetStreamAdaptor::getPosition() const {
+    const off64_t offset = AAsset_seek64(mAAsset, 0, SEEK_CUR);
+    if (offset == -1) {
+        ALOGE("getPosition failed!");
+        return 0;
+    }
+
+    return offset;
+}
+
+bool AAssetStreamAdaptor::seek(size_t position) {
+    if (AAsset_seek64(mAAsset, position, SEEK_SET) == -1) {
+        ALOGE("seek failed!");
+        return false;
+    }
+
+    return true;
+}
+
+bool AAssetStreamAdaptor::move(long offset) {
+    if (AAsset_seek64(mAAsset, offset, SEEK_CUR) == -1) {
+        ALOGE("move failed!");
+        return false;
+    }
+
+    return true;
+}
+
+size_t AAssetStreamAdaptor::read(void* buffer, size_t size) {
+    ssize_t amount;
+
+    if (!buffer) {
+        if (!size) {
+            return 0;
+        }
+
+        // asset->seek returns new total offset
+        // we want to return amount that was skipped
+        const off64_t oldOffset = AAsset_seek64(mAAsset, 0, SEEK_CUR);
+        if (oldOffset == -1) {
+            ALOGE("seek(oldOffset) failed!");
+            return 0;
+        }
+
+        const off64_t newOffset = AAsset_seek64(mAAsset, size, SEEK_CUR);
+        if (-1 == newOffset) {
+            ALOGE("seek(%zu) failed!", size);
+            return 0;
+        }
+        amount = newOffset - oldOffset;
+    } else {
+        amount = AAsset_read(mAAsset, buffer, size);
+    }
+
+    if (amount < 0) {
+        amount = 0;
+    }
+    return amount;
+}
+
+const void* AAssetStreamAdaptor::getMemoryBase() {
+    return AAsset_getBuffer(mAAsset);
+}
+
diff --git a/native/graphics/jni/aassetstreamadaptor.h b/native/graphics/jni/aassetstreamadaptor.h
new file mode 100644
index 0000000..c1fddb0
--- /dev/null
+++ b/native/graphics/jni/aassetstreamadaptor.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "SkStream.h"
+
+#include <android/asset_manager.h>
+
+// Like AssetStreamAdaptor, but operates on AAsset, a public NDK API.
+class AAssetStreamAdaptor : public SkStreamRewindable {
+public:
+    /**
+     * Create an SkStream that reads from an AAsset.
+     *
+     * The AAsset must remain open for the lifetime of the Adaptor. The Adaptor
+     * does *not* close the AAsset.
+     */
+    explicit AAssetStreamAdaptor(AAsset*);
+
+    bool rewind() override;
+    size_t read(void* buffer, size_t size) override;
+    bool hasLength() const override { return true; }
+    size_t getLength() const override;
+    bool hasPosition() const override;
+    size_t getPosition() const override;
+    bool seek(size_t position) override;
+    bool move(long offset) override;
+    bool isAtEnd() const override;
+    const void* getMemoryBase() override;
+protected:
+    SkStreamRewindable* onDuplicate() const override;
+
+private:
+    AAsset* mAAsset;
+};
+
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
new file mode 100644
index 0000000..2ef203d
--- /dev/null
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "aassetstreamadaptor.h"
+
+#include <android/asset_manager.h>
+#include <android/bitmap.h>
+#include <android/imagedecoder.h>
+#include <android/graphics/MimeType.h>
+#include <android/rect.h>
+#include <hwui/ImageDecoder.h>
+#include <log/log.h>
+#include <SkAndroidCodec.h>
+
+#include <fcntl.h>
+#include <optional>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+using namespace android;
+
+int ResultToErrorCode(SkCodec::Result result) {
+    switch (result) {
+        case SkCodec::kIncompleteInput:
+            return ANDROID_IMAGE_DECODER_INCOMPLETE;
+        case SkCodec::kErrorInInput:
+            return ANDROID_IMAGE_DECODER_ERROR;
+        case SkCodec::kInvalidInput:
+            return ANDROID_IMAGE_DECODER_INVALID_INPUT;
+        case SkCodec::kCouldNotRewind:
+            return ANDROID_IMAGE_DECODER_SEEK_ERROR;
+        case SkCodec::kUnimplemented:
+            return ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT;
+        case SkCodec::kInvalidConversion:
+            return ANDROID_IMAGE_DECODER_INVALID_CONVERSION;
+        case SkCodec::kInvalidParameters:
+            return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+        case SkCodec::kSuccess:
+            return ANDROID_IMAGE_DECODER_SUCCESS;
+        case SkCodec::kInvalidScale:
+            return ANDROID_IMAGE_DECODER_INVALID_SCALE;
+        case SkCodec::kInternalError:
+            return ANDROID_IMAGE_DECODER_INTERNAL_ERROR;
+    }
+}
+
+static int createFromStream(std::unique_ptr<SkStreamRewindable> stream, AImageDecoder** outDecoder) {
+    SkCodec::Result result;
+    auto codec = SkCodec::MakeFromStream(std::move(stream), &result, nullptr,
+                                         SkCodec::SelectionPolicy::kPreferAnimation);
+    auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec),
+            SkAndroidCodec::ExifOrientationBehavior::kRespect);
+    if (!androidCodec) {
+        return ResultToErrorCode(result);
+    }
+
+    *outDecoder = reinterpret_cast<AImageDecoder*>(new ImageDecoder(std::move(androidCodec)));
+    return ANDROID_IMAGE_DECODER_SUCCESS;
+}
+
+int AImageDecoder_createFromAAsset(AAsset* asset, AImageDecoder** outDecoder) {
+    if (!asset || !outDecoder) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+    *outDecoder = nullptr;
+
+    auto stream = std::make_unique<AAssetStreamAdaptor>(asset);
+    return createFromStream(std::move(stream), outDecoder);
+}
+
+static bool isSeekable(int descriptor) {
+    return ::lseek64(descriptor, 0, SEEK_CUR) != -1;
+}
+
+int AImageDecoder_createFromFd(int fd, AImageDecoder** outDecoder) {
+    if (fd <= 0 || !outDecoder) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    struct stat fdStat;
+    if (fstat(fd, &fdStat) == -1) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    if (!isSeekable(fd)) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    // SkFILEStream will close its descriptor. Duplicate it so the client will
+    // still be responsible for closing the original.
+    int dupDescriptor = fcntl(fd, F_DUPFD_CLOEXEC, 0);
+    FILE* file = fdopen(dupDescriptor, "r");
+    if (!file) {
+        close(dupDescriptor);
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    auto stream = std::unique_ptr<SkStreamRewindable>(new SkFILEStream(file));
+    return createFromStream(std::move(stream), outDecoder);
+}
+
+int AImageDecoder_createFromBuffer(const void* buffer, size_t length,
+                                   AImageDecoder** outDecoder) {
+    if (!buffer || !length  || !outDecoder) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+    *outDecoder = nullptr;
+
+    // The client is expected to keep the buffer alive as long as the
+    // AImageDecoder, so we do not need to copy the buffer.
+    auto stream = std::unique_ptr<SkStreamRewindable>(
+            new SkMemoryStream(buffer, length, false /* copyData */));
+    return createFromStream(std::move(stream), outDecoder);
+}
+
+static ImageDecoder* toDecoder(AImageDecoder* d) {
+    return reinterpret_cast<ImageDecoder*>(d);
+}
+
+// Note: This differs from the version in android_bitmap.cpp in that this
+// version returns kGray_8_SkColorType for ANDROID_BITMAP_FORMAT_A_8. SkCodec
+// allows decoding single channel images to gray, which Android then treats
+// as A_8/ALPHA_8.
+static SkColorType getColorType(AndroidBitmapFormat format) {
+    switch (format) {
+        case ANDROID_BITMAP_FORMAT_RGBA_8888:
+            return kN32_SkColorType;
+        case ANDROID_BITMAP_FORMAT_RGB_565:
+            return kRGB_565_SkColorType;
+        case ANDROID_BITMAP_FORMAT_RGBA_4444:
+            return kARGB_4444_SkColorType;
+        case ANDROID_BITMAP_FORMAT_A_8:
+            return kGray_8_SkColorType;
+        case ANDROID_BITMAP_FORMAT_RGBA_F16:
+            return kRGBA_F16_SkColorType;
+        default:
+            return kUnknown_SkColorType;
+    }
+}
+
+int AImageDecoder_setAndroidBitmapFormat(AImageDecoder* decoder, int32_t format) {
+    if (!decoder || format < ANDROID_BITMAP_FORMAT_NONE
+            || format > ANDROID_BITMAP_FORMAT_RGBA_F16) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+    return toDecoder(decoder)->setOutColorType(getColorType((AndroidBitmapFormat) format))
+            ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION;
+}
+
+const AImageDecoderHeaderInfo* AImageDecoder_getHeaderInfo(const AImageDecoder* decoder) {
+    return reinterpret_cast<const AImageDecoderHeaderInfo*>(decoder);
+}
+
+static const ImageDecoder* toDecoder(const AImageDecoderHeaderInfo* info) {
+    return reinterpret_cast<const ImageDecoder*>(info);
+}
+
+int32_t AImageDecoderHeaderInfo_getWidth(const AImageDecoderHeaderInfo* info) {
+    if (!info) {
+        return 0;
+    }
+    return toDecoder(info)->mCodec->getInfo().width();
+}
+
+int32_t AImageDecoderHeaderInfo_getHeight(const AImageDecoderHeaderInfo* info) {
+    if (!info) {
+        return 0;
+    }
+    return toDecoder(info)->mCodec->getInfo().height();
+}
+
+const char* AImageDecoderHeaderInfo_getMimeType(const AImageDecoderHeaderInfo* info) {
+    if (!info) {
+        return nullptr;
+    }
+    return getMimeType(toDecoder(info)->mCodec->getEncodedFormat());
+}
+
+bool AImageDecoderHeaderInfo_isAnimated(const AImageDecoderHeaderInfo* info) {
+    if (!info) {
+        return false;
+    }
+    return toDecoder(info)->mCodec->codec()->getFrameCount() > 1;
+}
+
+// FIXME: Share with getFormat in android_bitmap.cpp?
+static AndroidBitmapFormat getFormat(SkColorType colorType) {
+    switch (colorType) {
+        case kN32_SkColorType:
+            return ANDROID_BITMAP_FORMAT_RGBA_8888;
+        case kRGB_565_SkColorType:
+            return ANDROID_BITMAP_FORMAT_RGB_565;
+        case kARGB_4444_SkColorType:
+            return ANDROID_BITMAP_FORMAT_RGBA_4444;
+        case kAlpha_8_SkColorType:
+            return ANDROID_BITMAP_FORMAT_A_8;
+        case kRGBA_F16_SkColorType:
+            return ANDROID_BITMAP_FORMAT_RGBA_F16;
+        default:
+            return ANDROID_BITMAP_FORMAT_NONE;
+    }
+}
+
+AndroidBitmapFormat AImageDecoderHeaderInfo_getAndroidBitmapFormat(
+        const AImageDecoderHeaderInfo* info) {
+    if (!info) {
+        return ANDROID_BITMAP_FORMAT_NONE;
+    }
+    return getFormat(toDecoder(info)->mCodec->computeOutputColorType(kN32_SkColorType));
+}
+
+int AImageDecoderHeaderInfo_getAlphaFlags(const AImageDecoderHeaderInfo* info) {
+    if (!info) {
+        // FIXME: Better invalid?
+        return -1;
+    }
+    switch (toDecoder(info)->mCodec->getInfo().alphaType()) {
+        case kUnknown_SkAlphaType:
+            LOG_ALWAYS_FATAL("Invalid alpha type");
+            return -1;
+        case kUnpremul_SkAlphaType:
+            // fall through. premul is the default.
+        case kPremul_SkAlphaType:
+            return ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
+        case kOpaque_SkAlphaType:
+            return ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE;
+    }
+}
+
+SkAlphaType toAlphaType(int androidBitmapFlags) {
+    switch (androidBitmapFlags) {
+        case ANDROID_BITMAP_FLAGS_ALPHA_PREMUL:
+            return kPremul_SkAlphaType;
+        case ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL:
+            return kUnpremul_SkAlphaType;
+        case ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE:
+            return kOpaque_SkAlphaType;
+        default:
+            return kUnknown_SkAlphaType;
+    }
+}
+
+int AImageDecoder_setAlphaFlags(AImageDecoder* decoder, int alphaFlag) {
+    if (!decoder || alphaFlag < ANDROID_BITMAP_FLAGS_ALPHA_PREMUL
+            || alphaFlag > ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    return toDecoder(decoder)->setOutAlphaType(toAlphaType(alphaFlag))
+            ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION;
+}
+
+int AImageDecoder_setTargetSize(AImageDecoder* decoder, int width, int height) {
+    if (!decoder) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    return toDecoder(decoder)->setTargetSize(width, height)
+            ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_SCALE;
+}
+
+int AImageDecoder_setCrop(AImageDecoder* decoder, ARect crop) {
+    if (!decoder) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    SkIRect cropIRect;
+    cropIRect.setLTRB(crop.left, crop.top, crop.right, crop.bottom);
+    SkIRect* cropPtr = cropIRect == SkIRect::MakeEmpty() ? nullptr : &cropIRect;
+    return toDecoder(decoder)->setCropRect(cropPtr)
+            ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+}
+
+
+size_t AImageDecoder_getMinimumStride(AImageDecoder* decoder) {
+    if (!decoder) {
+        return 0;
+    }
+
+    SkImageInfo info = toDecoder(decoder)->getOutputInfo();
+    return info.minRowBytes();
+}
+
+int AImageDecoder_decodeImage(AImageDecoder* decoder,
+                              void* pixels, size_t stride,
+                              size_t size) {
+    if (!decoder || !pixels || !stride) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    ImageDecoder* imageDecoder = toDecoder(decoder);
+
+    const int height = imageDecoder->getOutputInfo().height();
+    const size_t minStride = AImageDecoder_getMinimumStride(decoder);
+    // If this calculation were to overflow, it would have been caught in
+    // setTargetSize.
+    if (stride < minStride || size < stride * (height - 1) + minStride) {
+        return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+    }
+
+    return ResultToErrorCode(imageDecoder->decode(pixels, stride));
+}
+
+void AImageDecoder_delete(AImageDecoder* decoder) {
+    delete toDecoder(decoder);
+}
diff --git a/native/graphics/jni/libjnigraphics.map.txt b/native/graphics/jni/libjnigraphics.map.txt
index a601d8a..bdd7f63 100644
--- a/native/graphics/jni/libjnigraphics.map.txt
+++ b/native/graphics/jni/libjnigraphics.map.txt
@@ -1,5 +1,22 @@
 LIBJNIGRAPHICS {
   global:
+    AImageDecoder_createFromAAsset;
+    AImageDecoder_createFromFd;
+    AImageDecoder_createFromBuffer;
+    AImageDecoder_delete;
+    AImageDecoder_setAndroidBitmapFormat;
+    AImageDecoder_setAlphaFlags;
+    AImageDecoder_getHeaderInfo;
+    AImageDecoder_getMinimumStride;
+    AImageDecoder_decodeImage;
+    AImageDecoder_setTargetSize;
+    AImageDecoder_setCrop;
+    AImageDecoderHeaderInfo_getWidth;
+    AImageDecoderHeaderInfo_getHeight;
+    AImageDecoderHeaderInfo_getMimeType;
+    AImageDecoderHeaderInfo_getAlphaFlags;
+    AImageDecoderHeaderInfo_isAnimated;
+    AImageDecoderHeaderInfo_getAndroidBitmapFormat;
     AndroidBitmap_getInfo;
     AndroidBitmap_lockPixels;
     AndroidBitmap_unlockPixels;
diff --git a/opengl/java/android/opengl/EGL14.java b/opengl/java/android/opengl/EGL14.java
index 728e6e1..90b46fd 100644
--- a/opengl/java/android/opengl/EGL14.java
+++ b/opengl/java/android/opengl/EGL14.java
@@ -18,11 +18,11 @@
 
 package android.opengl;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.SurfaceTexture;
 import android.view.Surface;
-import android.view.SurfaceView;
 import android.view.SurfaceHolder;
+import android.view.SurfaceView;
 
 /**
  * EGL 1.4
diff --git a/opengl/java/android/opengl/GLES20.java b/opengl/java/android/opengl/GLES20.java
index d66e7ac..e853e44 100644
--- a/opengl/java/android/opengl/GLES20.java
+++ b/opengl/java/android/opengl/GLES20.java
@@ -19,7 +19,7 @@
 
 package android.opengl;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /** OpenGL ES 2.0
  */
diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java
index 8a3e6a0..75131b0 100644
--- a/opengl/java/android/opengl/GLSurfaceView.java
+++ b/opengl/java/android/opengl/GLSurfaceView.java
@@ -16,7 +16,7 @@
 
 package android.opengl;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Trace;
 import android.util.AttributeSet;
diff --git a/opengl/java/javax/microedition/khronos/egl/EGL10.java b/opengl/java/javax/microedition/khronos/egl/EGL10.java
index 8a25170..ea571c7 100644
--- a/opengl/java/javax/microedition/khronos/egl/EGL10.java
+++ b/opengl/java/javax/microedition/khronos/egl/EGL10.java
@@ -16,8 +16,7 @@
 
 package javax.microedition.khronos.egl;
 
-import android.annotation.UnsupportedAppUsage;
-import java.lang.String;
+import android.compat.annotation.UnsupportedAppUsage;
 
 public interface EGL10 extends EGL {
     int EGL_SUCCESS                     = 0x3000;
diff --git a/packages/CarSystemUI/res/layout/sysui_primary_window.xml b/packages/CarSystemUI/res/layout/sysui_primary_window.xml
new file mode 100644
index 0000000..309d9ef
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/sysui_primary_window.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:background="@android:color/transparent"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
index ed945e7..6ec3854 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
@@ -18,14 +18,20 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.util.leak.LeakDetector;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
+import dagger.Lazy;
+
 /**
  * Car specific notification entry manager that does nothing when adding a notification.
  *
@@ -40,8 +46,13 @@
             NotifLog notifLog,
             NotificationGroupManager groupManager,
             NotificationRankingManager rankingManager,
-            KeyguardEnvironment keyguardEnvironment) {
-        super(notifLog, groupManager, rankingManager, keyguardEnvironment);
+            KeyguardEnvironment keyguardEnvironment,
+            FeatureFlags featureFlags,
+            Lazy<NotificationRowBinder> notificationRowBinderLazy,
+            Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
+            LeakDetector leakDetector) {
+        super(notifLog, groupManager, rankingManager, keyguardEnvironment, featureFlags,
+                notificationRowBinderLazy, notificationRemoteInputManagerLazy, leakDetector);
     }
 
     @Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java b/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java
new file mode 100644
index 0000000..e3e9ab7
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.os.Binder;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Controls the expansion state of the primary window which will contain all of the fullscreen sysui
+ * behavior. This window still has a collapsed state in order to watch for swipe events to expand
+ * this window for the notification panel.
+ */
+@Singleton
+public class SystemUIPrimaryWindowController implements
+        ConfigurationController.ConfigurationListener {
+
+    private final Context mContext;
+    private final Resources mResources;
+    private final WindowManager mWindowManager;
+
+    private final int mStatusBarHeight;
+    private final int mNavBarHeight;
+    private final int mDisplayHeight;
+    private ViewGroup mBaseLayout;
+    private WindowManager.LayoutParams mLp;
+    private WindowManager.LayoutParams mLpChanged;
+
+    @Inject
+    public SystemUIPrimaryWindowController(
+            Context context,
+            @Main Resources resources,
+            WindowManager windowManager,
+            ConfigurationController configurationController
+    ) {
+        mContext = context;
+        mResources = resources;
+        mWindowManager = windowManager;
+
+        Point display = new Point();
+        mWindowManager.getDefaultDisplay().getSize(display);
+        mDisplayHeight = display.y;
+
+        mStatusBarHeight = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height);
+        mNavBarHeight = mResources.getDimensionPixelSize(R.dimen.navigation_bar_height);
+
+        mLpChanged = new WindowManager.LayoutParams();
+        mBaseLayout = (ViewGroup) LayoutInflater.from(context)
+                .inflate(R.layout.sysui_primary_window, /* root= */ null, false);
+
+        configurationController.addCallback(this);
+    }
+
+    /** Returns the base view of the primary window. */
+    public ViewGroup getBaseLayout() {
+        return mBaseLayout;
+    }
+
+    /** Attaches the window to the window manager. */
+    public void attach() {
+        // Now that the status bar window encompasses the sliding panel and its
+        // translucent backdrop, the entire thing is made TRANSLUCENT and is
+        // hardware-accelerated.
+        mLp = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                mStatusBarHeight,
+                WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
+                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
+                PixelFormat.TRANSLUCENT);
+        mLp.token = new Binder();
+        mLp.gravity = Gravity.TOP;
+        mLp.setFitWindowInsetsTypes(/* types= */ 0);
+        mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+        mLp.setTitle("NotificationShade");
+        mLp.packageName = mContext.getPackageName();
+        mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+        mWindowManager.addView(mBaseLayout, mLp);
+        mLpChanged.copyFrom(mLp);
+    }
+
+    /** Sets the window to the expanded state. */
+    public void setWindowExpanded(boolean expanded) {
+        if (expanded) {
+            // TODO: Update this so that the windowing type gets the full height of the display
+            //  when we use MATCH_PARENT.
+            mLpChanged.height = mDisplayHeight + mNavBarHeight;
+        } else {
+            mLpChanged.height = mStatusBarHeight;
+        }
+        updateWindow();
+    }
+
+    /** Returns {@code true} if the window is expanded */
+    public boolean isWindowExpanded() {
+        return mLp.height != mStatusBarHeight;
+    }
+
+    private void updateWindow() {
+        if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
+            mWindowManager.updateViewLayout(mBaseLayout, mLp);
+        }
+    }
+}
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 3c3ebe2..4521f5f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.car;
 
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
 import android.animation.Animator;
@@ -107,6 +106,7 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -268,7 +268,6 @@
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
             BypassHeadsUpNotifier bypassHeadsUpNotifier,
-            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
             Lazy<NewNotifPipeline> newNotifPipeline,
             FalsingManager falsingManager,
             BroadcastDispatcher broadcastDispatcher,
@@ -334,6 +333,7 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
+            NotificationRowBinderImpl notificationRowBinder,
             DismissCallbackRegistry dismissCallbackRegistry,
             /* Car Settings injected components. */
             CarServiceProvider carServiceProvider,
@@ -355,7 +355,6 @@
                 headsUpManagerPhone,
                 dynamicPrivacyController,
                 bypassHeadsUpNotifier,
-                allowNotificationLongPress,
                 newNotifPipeline,
                 falsingManager,
                 broadcastDispatcher,
@@ -421,6 +420,7 @@
                 keyguardDismissUtil,
                 extensionController,
                 userInfoControllerImpl,
+                notificationRowBinder,
                 dismissCallbackRegistry);
         mScrimController = scrimController;
         mLockscreenLockIconController = lockscreenLockIconController;
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 a1eccce..e5a091f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.car;
 
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
 import android.content.Context;
@@ -67,6 +66,7 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -138,7 +138,6 @@
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
             BypassHeadsUpNotifier bypassHeadsUpNotifier,
-            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
             Lazy<NewNotifPipeline> newNotifPipeline,
             FalsingManager falsingManager,
             BroadcastDispatcher broadcastDispatcher,
@@ -204,6 +203,7 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
+            NotificationRowBinderImpl notificationRowBinder,
             DismissCallbackRegistry dismissCallbackRegistry,
             CarServiceProvider carServiceProvider,
             Lazy<PowerManagerHelper> powerManagerHelperLazy,
@@ -224,7 +224,6 @@
                 headsUpManagerPhone,
                 dynamicPrivacyController,
                 bypassHeadsUpNotifier,
-                allowNotificationLongPress,
                 newNotifPipeline,
                 falsingManager,
                 broadcastDispatcher,
@@ -289,6 +288,7 @@
                 keyguardDismissUtil,
                 extensionController,
                 userInfoControllerImpl,
+                notificationRowBinder,
                 dismissCallbackRegistry,
                 carServiceProvider,
                 powerManagerHelperLazy,
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 1e19786..ec445d4 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -160,7 +160,7 @@
         final int userId = UserHandle.myUserId();
         final List<VolumeInfo> volumes = mStorageManager.getVolumes();
         for (VolumeInfo volume : volumes) {
-            if (!volume.isMountedReadable()) continue;
+            if (!volume.isMountedReadable() || volume.getMountUserId() != userId) continue;
 
             final String rootId;
             final String title;
@@ -192,9 +192,8 @@
                     title = mStorageManager.getBestVolumeDescription(privateVol);
                     storageUuid = StorageManager.convert(privateVol.fsUuid);
                 }
-            } else if ((volume.getType() == VolumeInfo.TYPE_PUBLIC
-                            || volume.getType() == VolumeInfo.TYPE_STUB)
-                    && volume.getMountUserId() == userId) {
+            } else if (volume.getType() == VolumeInfo.TYPE_PUBLIC
+                    || volume.getType() == VolumeInfo.TYPE_STUB) {
                 rootId = volume.getFsUuid();
                 title = mStorageManager.getBestVolumeDescription(volume);
                 storageUuid = null;
diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
index 69bd0ed..ff00fb3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
@@ -16,8 +16,6 @@
 
 package com.android.settingslib;
 
-import static android.content.Context.TELEPHONY_SERVICE;
-
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -172,36 +170,38 @@
         }
     }
 
-    public static String getFormattedPhoneNumber(Context context, SubscriptionInfo subscriptionInfo) {
+    /**
+     * Format a phone number.
+     * @param subscriptionInfo {@link SubscriptionInfo} subscription information.
+     * @return Returns formatted phone number.
+     */
+    public static String getFormattedPhoneNumber(Context context,
+            SubscriptionInfo subscriptionInfo) {
         String formattedNumber = null;
         if (subscriptionInfo != null) {
-            final TelephonyManager telephonyManager =
-                    (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
-            final String rawNumber =
-                    telephonyManager.getLine1Number(subscriptionInfo.getSubscriptionId());
+            final TelephonyManager telephonyManager = context.getSystemService(
+                    TelephonyManager.class);
+            final String rawNumber = telephonyManager.createForSubscriptionId(
+                    subscriptionInfo.getSubscriptionId()).getLine1Number();
             if (!TextUtils.isEmpty(rawNumber)) {
                 formattedNumber = PhoneNumberUtils.formatNumber(rawNumber);
             }
-
         }
         return formattedNumber;
     }
 
     public static String getFormattedPhoneNumbers(Context context,
-            List<SubscriptionInfo> subscriptionInfo) {
+            List<SubscriptionInfo> subscriptionInfoList) {
         StringBuilder sb = new StringBuilder();
-        if (subscriptionInfo != null) {
-            final TelephonyManager telephonyManager =
-                    (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
-            final int count = subscriptionInfo.size();
-            for (int i = 0; i < count; i++) {
-                final String rawNumber = telephonyManager.getLine1Number(
-                        subscriptionInfo.get(i).getSubscriptionId());
+        if (subscriptionInfoList != null) {
+            final TelephonyManager telephonyManager = context.getSystemService(
+                    TelephonyManager.class);
+            final int count = subscriptionInfoList.size();
+            for (SubscriptionInfo subscriptionInfo : subscriptionInfoList) {
+                final String rawNumber = telephonyManager.createForSubscriptionId(
+                        subscriptionInfo.getSubscriptionId()).getLine1Number();
                 if (!TextUtils.isEmpty(rawNumber)) {
-                    sb.append(PhoneNumberUtils.formatNumber(rawNumber));
-                    if (i < count - 1) {
-                        sb.append("\n");
-                    }
+                    sb.append(PhoneNumberUtils.formatNumber(rawNumber)).append("\n");
                 }
             }
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index a2bd210..a784e04 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
@@ -148,17 +151,18 @@
     }
 
     public boolean connect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.connect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        // Downgrade priority as user is disconnecting the headset.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        if (mService == null) {
+            return false;
         }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -182,12 +186,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -197,11 +201,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
     boolean isA2dpPlaying() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
index bc03c34..8ca5a74 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothA2dpSink;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
@@ -116,18 +119,15 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        // Downgrade priority as user is disconnecting the headset.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -141,12 +141,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -156,11 +156,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index 560cb3b..d65b5da 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -112,18 +115,15 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        // Downgrade priority as user is disconnecting the headset.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -165,12 +165,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -180,11 +180,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index b4b55f3..9f1af66 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -146,17 +149,18 @@
     }
 
     public boolean connect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.connect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        // Downgrade priority as user is disconnecting the hearing aid.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        if (mService == null) {
+            return false;
         }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -180,12 +184,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -195,11 +199,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
index a372e23..678f2e3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -126,7 +129,7 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     @Override
@@ -134,11 +137,8 @@
         if (mService == null) {
             return false;
         }
-        // Downgrade priority as user is disconnecting the headset.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Override
@@ -154,13 +154,13 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     @Override
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -171,11 +171,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index 975a1e6..588083e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -99,13 +102,17 @@
     }
 
     public boolean connect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.connect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.disconnect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -119,12 +126,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) != CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -132,11 +139,11 @@
     public void setPreferred(BluetoothDevice device, boolean preferred) {
         if (mService == null) return;
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
index 95139a1..7d121aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -115,18 +118,15 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        // Downgrade priority as user is disconnecting.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -140,12 +140,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -155,11 +155,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
index 31a0eea..a96a4e7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -119,10 +121,8 @@
         if (mService == null) {
             return false;
         }
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -136,12 +136,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -155,7 +155,7 @@
                 mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
index 4ea0df6..56267fc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -129,7 +132,7 @@
             return false;
         }
         Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress());
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
@@ -137,7 +140,7 @@
         if (mService == null) {
             return false;
         }
-        return mService.disconnect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -151,12 +154,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -166,11 +169,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
index 3f920a8..f7c0bf5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -96,8 +98,10 @@
     }
 
     public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.disconnect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
index 0ca4d61..3022c5b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -112,17 +115,15 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -136,12 +137,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -151,11 +152,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 5b4ef3a..70b56ed 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -194,6 +194,14 @@
         }
     }
 
+    void dispatchDeviceAttributesChanged() {
+        synchronized (mCallbacks) {
+            for (DeviceCallback callback : mCallbacks) {
+                callback.onDeviceAttributesChanged();
+            }
+        }
+    }
+
     /**
      * Stop scan MediaDevice
      */
@@ -306,14 +314,12 @@
             }
             mCurrentConnectedDevice = connectDevice;
             updatePhoneMediaDeviceSummary();
-            dispatchDeviceListUpdate();
+            dispatchDeviceAttributesChanged();
         }
 
         @Override
         public void onDeviceAttributesChanged() {
-            addPhoneDeviceIfNecessary();
-            removePhoneMediaDeviceIfNecessary();
-            dispatchDeviceListUpdate();
+            dispatchDeviceAttributesChanged();
         }
     }
 
@@ -327,7 +333,7 @@
          *
          * @param devices MediaDevice list
          */
-        void onDeviceListUpdate(List<MediaDevice> devices);
+        default void onDeviceListUpdate(List<MediaDevice> devices) {};
 
         /**
          * Callback for notifying the connected device is changed.
@@ -338,6 +344,12 @@
          * {@link MediaDeviceState#STATE_CONNECTING},
          * {@link MediaDeviceState#STATE_DISCONNECTED}
          */
-        void onSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state);
+        default void onSelectedDeviceStateChanged(MediaDevice device,
+                @MediaDeviceState int state) {};
+
+        /**
+         * Callback for notifying the device attributes is changed.
+         */
+        default void onDeviceAttributesChanged() {};
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
index ebca962..853c77e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -38,7 +38,7 @@
         final SubscriptionManager subscriptionManager = context.getSystemService(
                 SubscriptionManager.class);
         final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
-                telephonyManager.getSubscriberId(subId));
+                telephonyManager.createForSubscriptionId(subId).getSubscriberId());
 
         if (!subscriptionManager.isActiveSubId(subId)) {
             Log.i(TAG, "Subscription is not active: " + subId);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
index 976445e..ccb6646 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
     @Test
     public void connect_shouldConnectBluetoothA2dpSink() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothA2dpSink() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
index 69c020d..9180760 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
     @Test
     public void connect_shouldConnectBluetoothHeadsetClient() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothHeadsetClient() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
index 6f66709..1425c38 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
     @Test
     public void connect_shouldConnectBluetoothMapClient() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothMapClient() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
index b21ec9c3..15f560b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
     @Test
     public void connect_shouldConnectBluetoothPbapClient() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothPbapClient() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
index ec88034..4f978a8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -66,13 +69,13 @@
     @Test
     public void connect_shouldConnectBluetoothSap() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothSap() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 98bb74a..894aa78 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -355,7 +355,7 @@
         mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_2);
 
         assertThat(mLocalMediaManager.getCurrentConnectedDevice()).isEqualTo(device2);
-        verify(mCallback).onDeviceListUpdate(any());
+        verify(mCallback).onDeviceAttributesChanged();
     }
 
     @Test
@@ -373,7 +373,7 @@
         mLocalMediaManager.registerCallback(mCallback);
         mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_1);
 
-        verify(mCallback, never()).onDeviceListUpdate(any());
+        verify(mCallback, never()).onDeviceAttributesChanged();
     }
 
     @Test
@@ -382,6 +382,6 @@
 
         mLocalMediaManager.mMediaDeviceCallback.onDeviceAttributesChanged();
 
-        verify(mCallback).onDeviceListUpdate(any());
+        verify(mCallback).onDeviceAttributesChanged();
     }
 }
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index f40d3a1..96a98dc 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -8,6 +8,7 @@
     libs: [
         "telephony-common",
         "ims-common",
+        "unsupportedappusage",
     ],
     static_libs: [
         "junit",
@@ -40,6 +41,7 @@
     libs: [
         "android.test.base",
         "android.test.mock",
+        "unsupportedappusage",
     ],
     resource_dirs: ["res"],
     aaptflags: [
diff --git a/packages/SettingsProvider/res/values/overlayable.xml b/packages/SettingsProvider/res/values/overlayable.xml
deleted file mode 100644
index dc41a77..0000000
--- a/packages/SettingsProvider/res/values/overlayable.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<!-- Copyright (C) 2019 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-  <overlayable name="SettingsToNotRestore">
-    <policy type="product|system|vendor">
-      <item type="array" name="restore_blocked_device_specific_settings" />
-      <item type="array" name="restore_blocked_global_settings" />
-      <item type="array" name="restore_blocked_secure_settings" />
-      <item type="array" name="restore_blocked_system_settings" />
-    </policy>
-  </overlayable>
-</resources>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 049b9f0..5636cc8 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -16,7 +16,7 @@
 
 package android.provider.settings.backup;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.provider.Settings;
 
 /** Information relating to the Secure settings which should be backed up */
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 89b19de..3f5b0da 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -16,7 +16,7 @@
 
 package android.provider.settings.backup;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.provider.Settings;
 
 /** Information about the system settings to back up */
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 94ab0f1..c5d4fa9 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -25,7 +25,7 @@
 import static android.provider.settings.validators.SettingsValidators.VIBRATION_INTENSITY_VALIDATOR;
 
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.hardware.display.ColorDisplayManager;
 import android.os.BatteryManager;
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 347d6c2..1c63efc 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -194,6 +194,9 @@
 
     <uses-permission android:name="android.permission.MANAGE_APPOPS" />
 
+    <!-- Permission required for storage tests - FuseDaemonHostTest -->
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+
     <!-- Permission needed to run network tests in CTS -->
     <uses-permission android:name="android.permission.MANAGE_TEST_NETWORKS" />
     <!-- Permission needed to test tcp keepalive offload. -->
diff --git a/packages/SystemUI/docs/broadcasts.md b/packages/SystemUI/docs/broadcasts.md
index 56a637f..28657f2 100644
--- a/packages/SystemUI/docs/broadcasts.md
+++ b/packages/SystemUI/docs/broadcasts.md
@@ -42,24 +42,29 @@
 
 ```kotlin
 /**
-    * Register a receiver for broadcast with the dispatcher
-    *
-    * @param receiver A receiver to dispatch the [Intent]
-    * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
-    *               It will only take into account actions and categories for filtering. It must
-    *               have at least one action.
-    * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. By default, it is the
-    *                main handler. Pass `null` to use the default.
-    * @param user A user handle to determine which broadcast should be dispatched to this receiver.
-    *             By default, it is the current user.
-    * @throws IllegalArgumentException if the filter has other constraints that are not actions or
-    *                                  categories or the filter has no actions.
-    */
+ * Register a receiver for broadcast with the dispatcher
+ *
+ * @param receiver A receiver to dispatch the [Intent]
+ * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
+ *               It will only take into account actions and categories for filtering. It must
+ *               have at least one action.
+ * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an
+ *                 executor in the main thread (default).
+ * @param user A user handle to determine which broadcast should be dispatched to this receiver.
+ *             By default, it is the current user.
+ * @throws IllegalArgumentException if the filter has other constraints that are not actions or
+ *                                  categories or the filter has no actions.
+ */
 @JvmOverloads
-fun registerReceiver(BroadcastReceiver, IntentFilter, Handler? = mainHandler, UserHandle = context.user)
+fun registerReceiver(
+        BroadcastReceiver, 
+        IntentFilter, 
+        Executor? = context.mainExecutor,
+        UserHandle = context.user
+) {
 ```
 
-All subscriptions are done with the same overloaded method. As specified in the doc, in order to pass a `UserHandle` with the default `Handler`, pass `null` for the `Handler`.
+All subscriptions are done with the same overloaded method. As specified in the doc, in order to pass a `UserHandle` with the default `Executor`, pass `null` for the `Executor`.
 
 In the same way as with `Context`, subscribing the same `BroadcastReceiver` for the same user using different filters will result on two subscriptions, not in replacing the filter.
 
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index 6ac9da4..995cb7d 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
+<!-- Copyright (C) 2020 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
diff --git a/packages/SystemUI/res/layout/global_screenshot_legacy.xml b/packages/SystemUI/res/layout/global_screenshot_legacy.xml
new file mode 100644
index 0000000..791c7ea
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_screenshot_legacy.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <ImageView android:id="@+id/global_screenshot_legacy_background"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:src="@android:color/black"
+        android:visibility="gone" />
+    <ImageView android:id="@+id/global_screenshot_legacy"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:background="@drawable/screenshot_panel"
+        android:visibility="gone"
+        android:adjustViewBounds="true" />
+    <ImageView android:id="@+id/global_screenshot_legacy_flash"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:src="@android:color/white"
+        android:visibility="gone" />
+    <com.android.systemui.screenshot.ScreenshotSelectorView
+        android:id="@+id/global_screenshot_legacy_selector"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone"
+        android:pointerIcon="crosshair"/>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
new file mode 100644
index 0000000..e91f840f
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_carousel.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<!-- Carousel for media controls -->
+<HorizontalScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/qs_media_height"
+    android:padding="@dimen/qs_media_padding"
+    android:scrollbars="none"
+    android:visibility="gone"
+    >
+    <LinearLayout
+        android:id="@+id/media_carousel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        >
+        <!-- QSMediaPlayers will be added here dynamically -->
+    </LinearLayout>
+</HorizontalScrollView>
diff --git a/packages/SystemUI/res/layout/people_strip.xml b/packages/SystemUI/res/layout/people_strip.xml
index f0ac08b..982aa8e 100644
--- a/packages/SystemUI/res/layout/people_strip.xml
+++ b/packages/SystemUI/res/layout/people_strip.xml
@@ -18,7 +18,10 @@
 <com.android.systemui.statusbar.notification.stack.PeopleHubView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="105dp">
+    android:layout_height="@dimen/notification_section_header_height"
+    android:focusable="true"
+    android:clickable="true"
+>
 
     <com.android.systemui.statusbar.notification.row.NotificationBackgroundView
         android:id="@+id/backgroundNormal"
@@ -34,199 +37,56 @@
         android:id="@+id/people_list"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:paddingTop="12dp"
-        android:paddingBottom="12dp"
         android:gravity="center"
         android:orientation="horizontal">
 
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
             android:layout_weight="1"
+            android:layout_marginStart="@dimen/notification_section_header_padding_left"
+            android:gravity="start"
+            android:textAlignment="gravity"
+            android:text="@string/notification_section_header_conversations"
+            android:textSize="12sp"
+            android:textColor="@color/notification_section_header_label_color"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
         />
 
-        <LinearLayout
-            android:layout_width="70dp"
-            android:layout_height="match_parent"
-            android:gravity="center_horizontal"
-            android:orientation="vertical"
-            android:visibility="invisible">
-
-            <ImageView
-                android:id="@+id/person_icon"
-                android:layout_width="36dp"
-                android:layout_height="36dp"
-                android:scaleType="fitCenter"
-            />
-
-            <TextView
-                android:id="@+id/person_name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingTop="8dp"
-                android:ellipsize="end"
-                android:maxLines="2"
-                android:textAlignment="center"
-            />
-
-        </LinearLayout>
-
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
+        <ImageView
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="8dp"
+            android:scaleType="fitCenter"
         />
 
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
+        <ImageView
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="8dp"
+            android:scaleType="fitCenter"
         />
 
-        <LinearLayout
-            android:layout_width="70dp"
-            android:layout_height="match_parent"
-            android:gravity="center_horizontal"
-            android:orientation="vertical"
-            android:visibility="invisible">
-
-            <ImageView
-                android:id="@+id/person_icon"
-                android:layout_width="36dp"
-                android:layout_height="36dp"
-                android:scaleType="fitCenter"
-            />
-
-            <TextView
-                android:id="@+id/person_name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingTop="8dp"
-                android:ellipsize="end"
-                android:maxLines="2"
-                android:textAlignment="center"
-            />
-
-        </LinearLayout>
-
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
+        <ImageView
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="8dp"
+            android:scaleType="fitCenter"
         />
 
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
+        <ImageView
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="8dp"
+            android:scaleType="fitCenter"
         />
 
-        <LinearLayout
-            android:layout_width="70dp"
-            android:layout_height="match_parent"
-            android:gravity="center_horizontal"
-            android:orientation="vertical"
-            android:visibility="invisible">
-
-            <ImageView
-                android:id="@+id/person_icon"
-                android:layout_width="36dp"
-                android:layout_height="36dp"
-                android:scaleType="fitCenter"
-            />
-
-            <TextView
-                android:id="@+id/person_name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingTop="8dp"
-                android:ellipsize="end"
-                android:maxLines="2"
-                android:textAlignment="center"
-            />
-
-        </LinearLayout>
-
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-        />
-
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-        />
-
-        <LinearLayout
-            android:layout_width="70dp"
-            android:layout_height="match_parent"
-            android:gravity="center_horizontal"
-            android:orientation="vertical"
-            android:visibility="invisible">
-
-            <ImageView
-                android:id="@+id/person_icon"
-                android:layout_width="36dp"
-                android:layout_height="36dp"
-                android:scaleType="fitCenter"
-            />
-
-            <TextView
-                android:id="@+id/person_name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingTop="8dp"
-                android:ellipsize="end"
-                android:maxLines="2"
-                android:textAlignment="center"
-            />
-
-        </LinearLayout>
-
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-        />
-
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-        />
-
-        <LinearLayout
-            android:layout_width="70dp"
-            android:layout_height="match_parent"
-            android:gravity="center_horizontal"
-            android:orientation="vertical"
-            android:visibility="invisible">
-
-            <ImageView
-                android:id="@+id/person_icon"
-                android:layout_width="36dp"
-                android:layout_height="36dp"
-                android:scaleType="fitCenter"
-            />
-
-            <TextView
-                android:id="@+id/person_name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingTop="8dp"
-                android:ellipsize="end"
-                android:maxLines="2"
-                android:textAlignment="center"
-            />
-
-        </LinearLayout>
-
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
+        <ImageView
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginEnd="8dp"
+            android:padding="8dp"
+            android:scaleType="fitCenter"
         />
 
     </LinearLayout>
@@ -236,4 +96,4 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
-</com.android.systemui.statusbar.notification.stack.PeopleHubView>
\ No newline at end of file
+</com.android.systemui.statusbar.notification.stack.PeopleHubView>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index da0323a..5a1151e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -288,6 +288,7 @@
     <!-- Dimensions related to screenshots -->
 
     <!-- The padding on the global screenshot background image -->
+    <dimen name="global_screenshot_legacy_bg_padding">20dp</dimen>
     <dimen name="global_screenshot_bg_padding">20dp</dimen>
     <dimen name="screenshot_action_container_corner_radius">10dp</dimen>
     <dimen name="screenshot_action_container_padding">20dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 52c0a26..4f532b7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1163,6 +1163,9 @@
     <!-- Section title for notifications that do not vibrate or make noise. [CHAR LIMIT=40] -->
     <string name="notification_section_header_gentle">Silent notifications</string>
 
+    <!-- Section title for conversational notifications. [CHAR LIMIT=40] -->
+    <string name="notification_section_header_conversations">Conversations</string>
+
     <!-- Content description for accessibility: Tapping this button will dismiss all gentle notifications [CHAR LIMIT=NONE] -->
     <string name="accessibility_notification_section_header_gentle_clear_all">Clear all silent notifications</string>
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 431862f..a58e3d7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1638,7 +1638,7 @@
         filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
-        broadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mHandler);
+        broadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, mHandler);
 
         final IntentFilter allUserFilter = new IntentFilter();
         allUserFilter.addAction(Intent.ACTION_USER_INFO_CHANGED);
@@ -1649,8 +1649,8 @@
         allUserFilter.addAction(ACTION_USER_UNLOCKED);
         allUserFilter.addAction(ACTION_USER_STOPPED);
         allUserFilter.addAction(ACTION_USER_REMOVED);
-        broadcastDispatcher.registerReceiver(mBroadcastAllReceiver, allUserFilter, mHandler,
-                UserHandle.ALL);
+        broadcastDispatcher.registerReceiverWithHandler(mBroadcastAllReceiver, allUserFilter,
+                mHandler, UserHandle.ALL);
 
         mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
         try {
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index eecc54c..bbe972d 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -121,6 +121,7 @@
 import com.android.systemui.util.leak.LeakDetector;
 import com.android.systemui.util.leak.LeakReporter;
 import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.wm.DisplayImeController;
 import com.android.systemui.wm.DisplayWindowController;
 import com.android.systemui.wm.SystemWindows;
 
@@ -321,6 +322,7 @@
     @Inject Lazy<StatusBar> mStatusBar;
     @Inject Lazy<DisplayWindowController> mDisplayWindowController;
     @Inject Lazy<SystemWindows> mSystemWindows;
+    @Inject Lazy<DisplayImeController> mDisplayImeController;
 
     @Inject
     public Dependency() {
@@ -509,6 +511,7 @@
         mProviders.put(StatusBar.class, mStatusBar::get);
         mProviders.put(DisplayWindowController.class, mDisplayWindowController::get);
         mProviders.put(SystemWindows.class, mSystemWindows::get);
+        mProviders.put(DisplayImeController.class, mDisplayImeController::get);
 
         // TODO(b/118592525): to support multi-display , we start to add something which is
         //                    per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/DumpController.kt b/packages/SystemUI/src/com/android/systemui/DumpController.kt
index 8c7075b..f14c4cd 100644
--- a/packages/SystemUI/src/com/android/systemui/DumpController.kt
+++ b/packages/SystemUI/src/com/android/systemui/DumpController.kt
@@ -30,6 +30,14 @@
 /**
  * Controller that allows any [Dumpable] to subscribe and be dumped along with other SystemUI
  * dependencies.
+ *
+ * To dump a specific dumpable on-demand:
+ *
+ * ```
+ * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService dependency DumpController <tag1>,<tag2>,<tag3>
+ * ```
+ *
+ * Where tag1, tag2, etc. are the tags of the dumpables you want to dump.
  */
 @Singleton
 class DumpController @Inject constructor() : Dumpable {
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 0e736dc..c533755 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -266,7 +266,7 @@
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_SWITCHED);
-        mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter, mHandler);
+        mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);
 
         mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index b083123..23d6458 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -18,7 +18,6 @@
 
 import android.app.AppOpsManager;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -64,7 +63,6 @@
     private H mBGHandler;
     private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>();
     private final ArrayMap<Integer, Set<Callback>> mCallbacksByCode = new ArrayMap<>();
-    private final PermissionFlagsCache mFlagsCache;
     private boolean mListening;
 
     @GuardedBy("mActiveItems")
@@ -81,17 +79,10 @@
     };
 
     @Inject
-    public AppOpsControllerImpl(Context context, @Background Looper bgLooper,
-            DumpController dumpController) {
-        this(context, bgLooper, new PermissionFlagsCache(context), dumpController);
-    }
-
-    @VisibleForTesting
-    protected AppOpsControllerImpl(Context context, Looper bgLooper, PermissionFlagsCache cache,
-            DumpController dumpController) {
+    public AppOpsControllerImpl(Context context,
+            @Background Looper bgLooper, DumpController dumpController) {
         mContext = context;
         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
-        mFlagsCache = cache;
         mBGHandler = new H(bgLooper);
         final int numOps = OPS.length;
         for (int i = 0; i < numOps; i++) {
@@ -209,7 +200,14 @@
             mNotedItems.remove(item);
             if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
         }
-        notifySuscribers(code, uid, packageName, false);
+        boolean active;
+        // Check if the item is also active
+        synchronized (mActiveItems) {
+            active = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
+        }
+        if (!active) {
+            notifySuscribers(code, uid, packageName, false);
+        }
     }
 
     private boolean addNoted(int code, int uid, String packageName) {
@@ -224,64 +222,13 @@
                 createdNew = true;
             }
         }
+        // We should keep this so we make sure it cannot time out.
+        mBGHandler.removeCallbacksAndMessages(item);
         mBGHandler.scheduleRemoval(item, NOTED_OP_TIME_DELAY_MS);
         return createdNew;
     }
 
     /**
-     * Does the app-op code refer to a user sensitive permission for the specified user id
-     * and package. Only user sensitive permission should be shown to the user by default.
-     *
-     * @param appOpCode The code of the app-op.
-     * @param uid The uid of the user.
-     * @param packageName The name of the package.
-     *
-     * @return {@code true} iff the app-op item is user sensitive
-     */
-    private boolean isUserSensitive(int appOpCode, int uid, String packageName) {
-        String permission = AppOpsManager.opToPermission(appOpCode);
-        if (permission == null) {
-            return false;
-        }
-        int permFlags = mFlagsCache.getPermissionFlags(permission,
-                packageName, UserHandle.getUserHandleForUid(uid));
-        return (permFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0;
-    }
-
-    /**
-     * Does the app-op item refer to an operation that should be shown to the user.
-     * Only specficic ops (like SYSTEM_ALERT_WINDOW) or ops that refer to user sensitive
-     * permission should be shown to the user by default.
-     *
-     * @param item The item
-     *
-     * @return {@code true} iff the app-op item should be shown to the user
-     */
-    private boolean isUserVisible(AppOpItem item) {
-        return isUserVisible(item.getCode(), item.getUid(), item.getPackageName());
-    }
-
-
-    /**
-     * Does the app-op, uid and package name, refer to an operation that should be shown to the
-     * user. Only specficic ops (like {@link AppOpsManager.OP_SYSTEM_ALERT_WINDOW}) or
-     * ops that refer to user sensitive permission should be shown to the user by default.
-     *
-     * @param item The item
-     *
-     * @return {@code true} iff the app-op for should be shown to the user
-     */
-    private boolean isUserVisible(int appOpCode, int uid, String packageName) {
-        // currently OP_SYSTEM_ALERT_WINDOW does not correspond to a platform permission
-        // which may be user senstive, so for now always show it to the user.
-        if (appOpCode == AppOpsManager.OP_SYSTEM_ALERT_WINDOW) {
-            return true;
-        }
-
-        return isUserSensitive(appOpCode, uid, packageName);
-    }
-
-    /**
      * Returns a copy of the list containing all the active AppOps that the controller tracks.
      *
      * @return List of active AppOps information
@@ -304,8 +251,8 @@
             final int numActiveItems = mActiveItems.size();
             for (int i = 0; i < numActiveItems; i++) {
                 AppOpItem item = mActiveItems.get(i);
-                if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId)
-                        && isUserVisible(item)) {
+                if ((userId == UserHandle.USER_ALL
+                        || UserHandle.getUserId(item.getUid()) == userId)) {
                     list.add(item);
                 }
             }
@@ -314,8 +261,8 @@
             final int numNotedItems = mNotedItems.size();
             for (int i = 0; i < numNotedItems; i++) {
                 AppOpItem item = mNotedItems.get(i);
-                if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId)
-                        && isUserVisible(item)) {
+                if ((userId == UserHandle.USER_ALL
+                        || UserHandle.getUserId(item.getUid()) == userId)) {
                     list.add(item);
                 }
             }
@@ -325,7 +272,21 @@
 
     @Override
     public void onOpActiveChanged(int code, int uid, String packageName, boolean active) {
-        if (updateActives(code, uid, packageName, active)) {
+        if (DEBUG) {
+            Log.w(TAG, String.format("onActiveChanged(%d,%d,%s,%s", code, uid, packageName,
+                    Boolean.toString(active)));
+        }
+        boolean activeChanged = updateActives(code, uid, packageName, active);
+        if (!activeChanged) return; // early return
+        // Check if the item is also noted, in that case, there's no update.
+        boolean alsoNoted;
+        synchronized (mNotedItems) {
+            alsoNoted = getAppOpItemLocked(mNotedItems, code, uid, packageName) != null;
+        }
+        // If active is true, we only send the update if the op is not actively noted (already true)
+        // If active is false, we only send the update if the op is not actively noted (prevent
+        // early removal)
+        if (!alsoNoted) {
             mBGHandler.post(() -> notifySuscribers(code, uid, packageName, active));
         }
     }
@@ -333,17 +294,23 @@
     @Override
     public void onOpNoted(int code, int uid, String packageName, int result) {
         if (DEBUG) {
-            Log.w(TAG, "Op: " + code + " with result " + AppOpsManager.MODE_NAMES[result]);
+            Log.w(TAG, "Noted op: " + code + " with result "
+                    + AppOpsManager.MODE_NAMES[result] + " for package " + packageName);
         }
         if (result != AppOpsManager.MODE_ALLOWED) return;
-        if (addNoted(code, uid, packageName)) {
+        boolean notedAdded = addNoted(code, uid, packageName);
+        if (!notedAdded) return; // early return
+        boolean alsoActive;
+        synchronized (mActiveItems) {
+            alsoActive = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
+        }
+        if (!alsoActive) {
             mBGHandler.post(() -> notifySuscribers(code, uid, packageName, true));
         }
     }
 
     private void notifySuscribers(int code, int uid, String packageName, boolean active) {
-        if (mCallbacksByCode.containsKey(code)
-                && isUserVisible(code, uid, packageName)) {
+        if (mCallbacksByCode.containsKey(code)) {
             if (DEBUG) Log.d(TAG, "Notifying of change in package " + packageName);
             for (Callback cb: mCallbacksByCode.get(code)) {
                 cb.onActiveStateChanged(code, uid, packageName, active);
@@ -368,7 +335,7 @@
 
     }
 
-    protected final class H extends Handler {
+    protected class H extends Handler {
         H(Looper looper) {
             super(looper);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt b/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
deleted file mode 100644
index f02c7af..0000000
--- a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.appops
-
-import android.content.Context
-import android.content.pm.PackageManager
-import android.os.UserHandle
-import android.util.ArrayMap
-import com.android.internal.annotations.VisibleForTesting
-
-private data class PermissionFlag(val flag: Int, val timestamp: Long)
-
-private data class PermissionFlagKey(
-    val permission: String,
-    val packageName: String,
-    val user: UserHandle
-)
-
-internal const val CACHE_EXPIRATION = 10000L
-
-/**
- * Cache for PackageManager's PermissionFlags.
- *
- * Flags older than [CACHE_EXPIRATION] will be retrieved again.
- */
-internal open class PermissionFlagsCache(context: Context) {
-    private val packageManager = context.packageManager
-    private val permissionFlagsCache = ArrayMap<PermissionFlagKey, PermissionFlag>()
-
-    /**
-     * Retrieve permission flags from cache or PackageManager. There parameters will be passed
-     * directly to [PackageManager].
-     *
-     * Calls to this method should be done from a background thread.
-     */
-    fun getPermissionFlags(permission: String, packageName: String, user: UserHandle): Int {
-        val key = PermissionFlagKey(permission, packageName, user)
-        val now = getCurrentTime()
-        val value = permissionFlagsCache.getOrPut(key) {
-            PermissionFlag(getFlags(key), now)
-        }
-        if (now - value.timestamp > CACHE_EXPIRATION) {
-            val newValue = PermissionFlag(getFlags(key), now)
-            permissionFlagsCache.put(key, newValue)
-            return newValue.flag
-        } else {
-            return value.flag
-        }
-    }
-
-    private fun getFlags(key: PermissionFlagKey) =
-            packageManager.getPermissionFlags(key.permission, key.packageName, key.user)
-
-    @VisibleForTesting
-    protected open fun getCurrentTime() = System.currentTimeMillis()
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 8cb0cc5..cedf7c3 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.content.IntentFilter
 import android.os.Handler
+import android.os.HandlerExecutor
 import android.os.Looper
 import android.os.Message
 import android.os.UserHandle
@@ -32,13 +33,14 @@
 import com.android.systemui.dagger.qualifiers.Main
 import java.io.FileDescriptor
 import java.io.PrintWriter
+import java.util.concurrent.Executor
 import javax.inject.Inject
 import javax.inject.Singleton
 
 data class ReceiverData(
     val receiver: BroadcastReceiver,
     val filter: IntentFilter,
-    val handler: Handler,
+    val executor: Executor,
     val user: UserHandle
 )
 
@@ -76,8 +78,33 @@
      * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
      *               It will only take into account actions and categories for filtering. It must
      *               have at least one action.
-     * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. By default, it is the
-     *                main handler. Pass `null` to use the default.
+     * @param handler A handler to dispatch [BroadcastReceiver.onReceive].
+     * @param user A user handle to determine which broadcast should be dispatched to this receiver.
+     *             By default, it is the current user.
+     * @throws IllegalArgumentException if the filter has other constraints that are not actions or
+     *                                  categories or the filter has no actions.
+     */
+    @Deprecated(message = "Replacing Handler for Executor in SystemUI",
+            replaceWith = ReplaceWith("registerReceiver(receiver, filter, executor, user)"))
+    @JvmOverloads
+    fun registerReceiverWithHandler(
+        receiver: BroadcastReceiver,
+        filter: IntentFilter,
+        handler: Handler,
+        user: UserHandle = context.user
+    ) {
+        registerReceiver(receiver, filter, HandlerExecutor(handler), user)
+    }
+
+    /**
+     * Register a receiver for broadcast with the dispatcher
+     *
+     * @param receiver A receiver to dispatch the [Intent]
+     * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
+     *               It will only take into account actions and categories for filtering. It must
+     *               have at least one action.
+     * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an
+     *                 executor in the main thread (default).
      * @param user A user handle to determine which broadcast should be dispatched to this receiver.
      *             By default, it is the current user.
      * @throws IllegalArgumentException if the filter has other constraints that are not actions or
@@ -85,15 +112,15 @@
      */
     @JvmOverloads
     fun registerReceiver(
-        receiver: BroadcastReceiver,
-        filter: IntentFilter,
-        handler: Handler? = mainHandler,
-        user: UserHandle = context.user
+            receiver: BroadcastReceiver,
+            filter: IntentFilter,
+            executor: Executor? = context.mainExecutor,
+            user: UserHandle = context.user
     ) {
         checkFilter(filter)
         this.handler
                 .obtainMessage(MSG_ADD_RECEIVER,
-                ReceiverData(receiver, filter, handler ?: mainHandler, user))
+                        ReceiverData(receiver, filter, executor ?: context.mainExecutor, user))
                 .sendToTarget()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index b2942bb..0c631aa 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -193,7 +193,7 @@
                         it.filter.hasAction(intent.action) &&
                             it.filter.matchCategories(intent.categories) == null }
                     ?.forEach {
-                        it.handler.post {
+                        it.executor.execute {
                             if (DEBUG) Log.w(TAG,
                                     "[$index] Dispatching ${intent.action} to ${it.receiver}")
                             it.receiver.pendingResult = pendingResult
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 7cd29ea..d835ee1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -344,11 +344,14 @@
         mSavedBubbleKeysPerUser = new SparseSetArray<>();
         mCurrentUserId = mNotifUserManager.getCurrentUserId();
         mNotifUserManager.addUserChangedListener(
-                newUserId -> {
-                    saveBubbles(mCurrentUserId);
-                    mBubbleData.dismissAll(DISMISS_USER_CHANGED);
-                    restoreBubbles(newUserId);
-                    mCurrentUserId = newUserId;
+                new NotificationLockscreenUserManager.UserChangedListener() {
+                    @Override
+                    public void onUserChanged(int newUserId) {
+                        BubbleController.this.saveBubbles(mCurrentUserId);
+                        mBubbleData.dismissAll(DISMISS_USER_CHANGED);
+                        BubbleController.this.restoreBubbles(newUserId);
+                        mCurrentUserId = newUserId;
+                    }
                 });
 
         mUserCreatedBubbles = new HashSet<>();
@@ -743,9 +746,16 @@
             return !isAutogroupSummary;
         } else {
             // If it's not a user dismiss it's a cancel.
+            for (int i = 0; i < bubbleChildren.size(); i++) {
+                // First check if any of these are user-created (i.e. experimental bubbles)
+                if (mUserCreatedBubbles.contains(bubbleChildren.get(i).getKey())) {
+                    // Experimental bubble! Intercept the removal.
+                    return true;
+                }
+            }
+            // Not an experimental bubble, safe to remove.
             mBubbleData.removeSuppressedSummary(groupKey);
-
-            // Remove any associated bubble children.
+            // Remove any associated bubble children with the summary.
             for (int i = 0; i < bubbleChildren.size(); i++) {
                 Bubble bubbleChild = bubbleChildren.get(i);
                 mBubbleData.notificationEntryRemoved(bubbleChild.getEntry(),
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 97224f1..ccbbb24 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -111,7 +111,10 @@
     }
 
     private final Context mContext;
+    /** Bubbles that are actively in the stack. */
     private final List<Bubble> mBubbles;
+    /** Bubbles that are being loaded but haven't been added to the stack just yet. */
+    private final List<Bubble> mPendingBubbles;
     private Bubble mSelectedBubble;
     private boolean mExpanded;
     private final int mMaxBubbles;
@@ -143,6 +146,7 @@
     public BubbleData(Context context) {
         mContext = context;
         mBubbles = new ArrayList<>();
+        mPendingBubbles = new ArrayList<>();
         mStateChange = new Update(mBubbles);
         mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered);
     }
@@ -188,7 +192,15 @@
     Bubble getOrCreateBubble(NotificationEntry entry) {
         Bubble bubble = getBubbleWithKey(entry.getKey());
         if (bubble == null) {
+            // Check for it in pending
+            for (int i = 0; i < mPendingBubbles.size(); i++) {
+                Bubble b = mPendingBubbles.get(i);
+                if (b.getKey().equals(entry.getKey())) {
+                    return b;
+                }
+            }
             bubble = new Bubble(entry);
+            mPendingBubbles.add(bubble);
         } else {
             bubble.setEntry(entry);
         }
@@ -204,7 +216,7 @@
         if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "notificationEntryUpdated: " + bubble);
         }
-
+        mPendingBubbles.remove(bubble); // No longer pending once we're here
         Bubble prevBubble = getBubbleWithKey(bubble.getKey());
         suppressFlyout |= !shouldShowFlyout(bubble.getEntry());
 
@@ -377,6 +389,12 @@
     }
 
     private void doRemove(String key, @DismissReason int reason) {
+        //  If it was pending remove it
+        for (int i = 0; i < mPendingBubbles.size(); i++) {
+            if (mPendingBubbles.get(i).getKey().equals(key)) {
+                mPendingBubbles.remove(mPendingBubbles.get(i));
+            }
+        }
         int indexToRemove = indexForKey(key);
         if (indexToRemove == -1) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index 59d68bc..6528f37 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -97,13 +97,11 @@
     private boolean mSpringingBubbleToTouch = false;
 
     private int mExpandedViewPadding;
-    private float mLauncherGridDiff;
 
     public ExpandedAnimationController(Point displaySize, int expandedViewPadding,
             int orientation) {
         updateOrientation(orientation, displaySize);
         mExpandedViewPadding = expandedViewPadding;
-        mLauncherGridDiff = 30f;
     }
 
     /**
@@ -569,15 +567,7 @@
      * @return Space between bubbles in row above expanded view.
      */
     private float getSpaceBetweenBubbles() {
-        /**
-         * Ordered left to right:
-         *  Screen edge
-         *      [mExpandedViewPadding]
-         *  Expanded view edge
-         *      [launcherGridDiff] --- arbitrary value until launcher exports widths
-         *  Launcher's app icon grid edge that we must match
-         */
-        final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
+        final float rowMargins = mExpandedViewPadding * 2;
         final float maxRowWidth = getWidthForDisplayingBubbles() - rowMargins;
 
         final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
index 6744d74..20917bd 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
@@ -21,9 +21,12 @@
 import com.android.systemui.appops.AppOpsControllerImpl;
 import com.android.systemui.classifier.FalsingManagerProxy;
 import com.android.systemui.doze.DozeHost;
+import com.android.systemui.globalactions.GlobalActionsComponent;
+import com.android.systemui.globalactions.GlobalActionsImpl;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.PowerNotificationWarnings;
@@ -99,6 +102,17 @@
     /**
      */
     @Binds
+    public abstract GlobalActions provideGlobalActions(GlobalActionsImpl controllerImpl);
+
+    /**
+     */
+    @Binds
+    public abstract GlobalActions.GlobalActionsManager provideGlobalActionsManager(
+            GlobalActionsComponent controllerImpl);
+
+    /**
+     */
+    @Binds
     public abstract LocationController provideLocationController(
             LocationControllerImpl controllerImpl);
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 8d10552..53a23b8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -20,7 +20,6 @@
 
 import android.app.INotificationManager;
 import android.content.Context;
-import android.content.pm.IPackageManager;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
@@ -115,13 +114,6 @@
     /** */
     @Singleton
     @Provides
-    public IPackageManager provideIPackageManager() {
-        return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
-    }
-
-    /** */
-    @Singleton
-    @Provides
     public LayoutInflater providerLayoutInflater(Context context) {
         return LayoutInflater.from(context);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index bb12b53..3aa14a3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -26,16 +26,24 @@
 import android.app.NotificationManager;
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
+import android.app.trust.TrustManager;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.IPackageManager;
 import android.content.res.Resources;
 import android.hardware.SensorPrivacyManager;
+import android.media.AudioManager;
+import android.net.ConnectivityManager;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.Vibrator;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
 import android.view.IWindowManager;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
@@ -65,6 +73,12 @@
         return context.getSystemService(AccessibilityManager.class);
     }
 
+    @Provides
+    @Singleton
+    static ActivityManager provideActivityManager(Context context) {
+        return context.getSystemService(ActivityManager.class);
+    }
+
     @Singleton
     @Provides
     static AlarmManager provideAlarmManager(Context context) {
@@ -73,10 +87,21 @@
 
     @Provides
     @Singleton
-    static ActivityManager provideActivityManager(Context context) {
-        return context.getSystemService(ActivityManager.class);
+    static AudioManager provideAudioManager(Context context) {
+        return context.getSystemService(AudioManager.class);
     }
 
+    @Provides
+    @Singleton
+    static ConnectivityManager provideConnectivityManagager(Context context) {
+        return context.getSystemService(ConnectivityManager.class);
+    }
+
+    @Provides
+    @Singleton
+    static ContentResolver provideContentResolver(Context context) {
+        return context.getContentResolver();
+    }
 
     @Provides
     @DisplayId
@@ -123,6 +148,13 @@
         return WindowManagerGlobal.getWindowManagerService();
     }
 
+    /** */
+    @Singleton
+    @Provides
+    public IPackageManager provideIPackageManager() {
+        return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+    }
+
     @Singleton
     @Provides
     static KeyguardManager provideKeyguardManager(Context context) {
@@ -177,6 +209,31 @@
 
     @Provides
     @Singleton
+    static TelecomManager provideTelecomManager(Context context) {
+        return context.getSystemService(TelecomManager.class);
+    }
+
+    @Provides
+    @Singleton
+    static TelephonyManager provideTelephonyManager(Context context) {
+        return context.getSystemService(TelephonyManager.class);
+    }
+
+    @Provides
+    @Singleton
+    static TrustManager provideTrustManager(Context context) {
+        return context.getSystemService(TrustManager.class);
+    }
+
+    @Provides
+    @Singleton
+    @Nullable
+    static Vibrator provideVibrator(Context context) {
+        return context.getSystemService(Vibrator.class);
+    }
+
+    @Provides
+    @Singleton
     static UserManager provideUserManager(Context context) {
         return context.getSystemService(UserManager.class);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 58ddda9..b195238 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -31,6 +31,8 @@
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
 import com.android.systemui.statusbar.notification.people.PeopleHubModule;
 import com.android.systemui.statusbar.phone.KeyguardLiftController;
@@ -82,6 +84,11 @@
                 keyguardUpdateMonitor, dumpController);
     }
 
+    /** */
+    @Binds
+    public abstract NotificationRowBinder bindNotificationRowBinder(
+            NotificationRowBinderImpl notificationRowBinder);
+
     @Singleton
     @Provides
     static SysUiState provideSysUiState() {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index c9faf69..4e88726 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -91,7 +91,7 @@
         if (mDebuggable) {
             IntentFilter filter = new IntentFilter();
             filter.addAction(ACTION_AOD_BRIGHTNESS);
-            mBroadcastDispatcher.registerReceiver(this, filter, handler, UserHandle.ALL);
+            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, handler, UserHandle.ALL);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index 19b6f82..e949007 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -19,7 +19,6 @@
 import android.os.ServiceManager;
 
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.Dependency;
 import com.android.systemui.SystemUI;
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
@@ -29,6 +28,7 @@
 import com.android.systemui.statusbar.policy.ExtensionController.Extension;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 import javax.inject.Singleton;
 
 /**
@@ -38,23 +38,29 @@
 public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager {
 
     private final CommandQueue mCommandQueue;
+    private final ExtensionController mExtensionController;
+    private final Provider<GlobalActions> mGlobalActionsProvider;
     private GlobalActions mPlugin;
     private Extension<GlobalActions> mExtension;
     private IStatusBarService mBarService;
 
     @Inject
-    public GlobalActionsComponent(Context context, CommandQueue commandQueue) {
+    public GlobalActionsComponent(Context context, CommandQueue commandQueue,
+            ExtensionController extensionController,
+            Provider<GlobalActions> globalActionsProvider) {
         super(context);
         mCommandQueue = commandQueue;
+        mExtensionController = extensionController;
+        mGlobalActionsProvider = globalActionsProvider;
     }
 
     @Override
     public void start() {
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
-        mExtension = Dependency.get(ExtensionController.class).newExtension(GlobalActions.class)
+        mExtension = mExtensionController.newExtension(GlobalActions.class)
                 .withPlugin(GlobalActions.class)
-                .withDefault(() -> new GlobalActionsImpl(mContext, mCommandQueue))
+                .withDefault(mGlobalActionsProvider::get)
                 .withCallback(this::onExtensionCallback)
                 .build();
         mPlugin = mExtension.get();
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 3ccad64..fc19fa0 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -21,8 +21,10 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.Dialog;
+import android.app.IActivityManager;
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
@@ -30,11 +32,13 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.UserInfo;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
@@ -44,13 +48,11 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.Vibrator;
 import android.provider.Settings;
-import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
 import android.sysprop.TelephonyProperties;
 import android.telecom.TelecomManager;
@@ -92,6 +94,7 @@
 import com.android.systemui.MultiListLayout.MultiListAdapter;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
@@ -106,6 +109,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.inject.Inject;
+
 /**
  * Helper to show the global actions dialog.  Each item is an {@link Action} that
  * may show depending on whether the keyguard is showing, and whether the device
@@ -146,6 +151,13 @@
     private final LockPatternUtils mLockPatternUtils;
     private final KeyguardManager mKeyguardManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final ContentResolver mContentResolver;
+    private final Resources mResources;
+    private final UserManager mUserManager;
+    private final TrustManager mTrustManager;
+    private final IActivityManager mIActivityManager;
+    private final TelecomManager mTelecomManager;
+    private final MetricsLogger mMetricsLogger;
 
     private ArrayList<Action> mItems;
     private ActionsDialog mDialog;
@@ -161,8 +173,6 @@
     private boolean mIsWaitingForEcmExit = false;
     private boolean mHasTelephony;
     private boolean mHasVibrator;
-    private boolean mHasLogoutButton;
-    private boolean mHasLockdownButton;
     private final boolean mShowSilentToggle;
     private final EmergencyAffordanceManager mEmergencyAffordanceManager;
     private final ScreenshotHelper mScreenshotHelper;
@@ -173,17 +183,32 @@
     /**
      * @param context everything needs a context :(
      */
-    public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs) {
+    @Inject
+    public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs,
+            AudioManager audioManager, IDreamManager iDreamManager,
+            DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils,
+            KeyguardManager keyguardManager, BroadcastDispatcher broadcastDispatcher,
+            ConnectivityManager connectivityManager, TelephonyManager telephonyManager,
+            ContentResolver contentResolver, @Nullable Vibrator vibrator, @Main Resources resources,
+            ConfigurationController configurationController, ActivityStarter activityStarter,
+            KeyguardStateController keyguardStateController, UserManager userManager,
+            TrustManager trustManager, IActivityManager iActivityManager,
+            TelecomManager telecomManager, MetricsLogger metricsLogger) {
         mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
         mWindowManagerFuncs = windowManagerFuncs;
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        mDreamManager = IDreamManager.Stub.asInterface(
-                ServiceManager.getService(DreamService.DREAM_SERVICE));
-        mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
-                Context.DEVICE_POLICY_SERVICE);
-        mLockPatternUtils = new LockPatternUtils(mContext);
-        mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-        mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
+        mAudioManager = audioManager;
+        mDreamManager = iDreamManager;
+        mDevicePolicyManager = devicePolicyManager;
+        mLockPatternUtils = lockPatternUtils;
+        mKeyguardManager = keyguardManager;
+        mBroadcastDispatcher = broadcastDispatcher;
+        mContentResolver = contentResolver;
+        mResources = resources;
+        mUserManager = userManager;
+        mTrustManager = trustManager;
+        mIActivityManager = iActivityManager;
+        mTelecomManager = telecomManager;
+        mMetricsLogger = metricsLogger;
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -192,32 +217,25 @@
         filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
         mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
 
-        ConnectivityManager cm = (ConnectivityManager)
-                context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+        mHasTelephony = connectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
 
         // get notified of phone state changes
-        TelephonyManager telephonyManager =
-                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
-        mContext.getContentResolver().registerContentObserver(
+        contentResolver.registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
                 mAirplaneModeObserver);
-        Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
         mHasVibrator = vibrator != null && vibrator.hasVibrator();
 
-        mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean(
+        mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean(
                 R.bool.config_useFixedVolume);
 
         mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
         mScreenshotHelper = new ScreenshotHelper(context);
         mScreenRecordHelper = new ScreenRecordHelper(context);
 
-        Dependency.get(ConfigurationController.class).addCallback(this);
+        configurationController.addCallback(this);
 
-        mActivityStarter = Dependency.get(ActivityStarter.class);
-        KeyguardStateController keyguardStateController =
-                Dependency.get(KeyguardStateController.class);
+        mActivityStarter = activityStarter;
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onUnlockedChanged() {
@@ -343,12 +361,9 @@
         onAirplaneModeChanged();
 
         mItems = new ArrayList<Action>();
-        String[] defaultActions = mContext.getResources().getStringArray(
-                R.array.config_globalActionsList);
+        String[] defaultActions = mResources.getStringArray(R.array.config_globalActionsList);
 
         ArraySet<String> addedKeys = new ArraySet<String>();
-        mHasLogoutButton = false;
-        mHasLockdownButton = false;
         for (int i = 0; i < defaultActions.length; i++) {
             String actionKey = defaultActions[i];
             if (addedKeys.contains(actionKey)) {
@@ -360,7 +375,7 @@
             } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
                 mItems.add(mAirplaneModeOn);
             } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
-                if (Settings.Global.getInt(mContext.getContentResolver(),
+                if (Settings.Global.getInt(mContentResolver,
                         Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
                     mItems.add(new BugReportAction());
                 }
@@ -375,11 +390,10 @@
             } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
                 mItems.add(getSettingsAction());
             } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
-                if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                if (Settings.Secure.getIntForUser(mContentResolver,
                             Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, getCurrentUser().id) != 0
                         && shouldDisplayLockdown()) {
                     mItems.add(getLockdownAction());
-                    mHasLockdownButton = true;
                 }
             } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
                 mItems.add(getVoiceAssistAction());
@@ -393,7 +407,6 @@
                 if (mDevicePolicyManager.isLogoutEnabled()
                         && getCurrentUser().id != UserHandle.USER_SYSTEM) {
                     mItems.add(new LogoutAction());
-                    mHasLogoutButton = true;
                 }
             } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) {
                 if (!mEmergencyAffordanceManager.needsEmergencyAffordance()) {
@@ -474,8 +487,7 @@
 
         @Override
         public boolean onLongPress() {
-            UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-            if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+            if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
                 mWindowManagerFuncs.reboot(true);
                 return true;
             }
@@ -560,9 +572,8 @@
 
         @Override
         public void onPress() {
-            MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU);
-            Intent intent = mContext.getSystemService(TelecomManager.class)
-                    .createLaunchEmergencyDialerIntent(null /* number */);
+            mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU);
+            Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent(null /* number */);
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
                     | Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -579,8 +590,7 @@
 
         @Override
         public boolean onLongPress() {
-            UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-            if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+            if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
                 mWindowManagerFuncs.reboot(true);
                 return true;
             }
@@ -618,8 +628,7 @@
                 @Override
                 public void run() {
                     mScreenshotHelper.takeScreenshot(1, true, true, mHandler, null);
-                    MetricsLogger.action(mContext,
-                            MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
+                    mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
                 }
             }, 500);
         }
@@ -666,11 +675,11 @@
                 public void run() {
                     try {
                         // Take an "interactive" bugreport.
-                        MetricsLogger.action(mContext,
+                        mMetricsLogger.action(
                                 MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
-                        if (!ActivityManager.getService().launchBugReportHandlerApp()) {
+                        if (!mIActivityManager.launchBugReportHandlerApp()) {
                             Log.w(TAG, "Bugreport handler could not be launched");
-                            ActivityManager.getService().requestInteractiveBugReport();
+                            mIActivityManager.requestInteractiveBugReport();
                         }
                     } catch (RemoteException e) {
                     }
@@ -687,8 +696,8 @@
             }
             try {
                 // Take a "full" bugreport.
-                MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
-                ActivityManager.getService().requestFullBugReport();
+                mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
+                mIActivityManager.requestFullBugReport();
             } catch (RemoteException e) {
             }
             return false;
@@ -726,8 +735,8 @@
             mHandler.postDelayed(() -> {
                 try {
                     int currentUserId = getCurrentUser().id;
-                    ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM);
-                    ActivityManager.getService().stopUser(currentUserId, true /*force*/, null);
+                    mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
+                    mIActivityManager.stopUser(currentUserId, true /*force*/, null);
                 } catch (RemoteException re) {
                     Log.e(TAG, "Couldn't logout user " + re);
                 }
@@ -834,20 +843,18 @@
     }
 
     private void lockProfiles() {
-        final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        final TrustManager tm = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
         final int currentUserId = getCurrentUser().id;
-        final int[] profileIds = um.getEnabledProfileIds(currentUserId);
+        final int[] profileIds = mUserManager.getEnabledProfileIds(currentUserId);
         for (final int id : profileIds) {
             if (id != currentUserId) {
-                tm.setDeviceLockedForUser(id, true);
+                mTrustManager.setDeviceLockedForUser(id, true);
             }
         }
     }
 
     private UserInfo getCurrentUser() {
         try {
-            return ActivityManager.getService().getCurrentUser();
+            return mIActivityManager.getCurrentUser();
         } catch (RemoteException re) {
             return null;
         }
@@ -859,9 +866,8 @@
     }
 
     private void addUsersToMenu(ArrayList<Action> items) {
-        UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        if (um.isUserSwitcherEnabled()) {
-            List<UserInfo> users = um.getUsers();
+        if (mUserManager.isUserSwitcherEnabled()) {
+            List<UserInfo> users = mUserManager.getUsers();
             UserInfo currentUser = getCurrentUser();
             for (final UserInfo user : users) {
                 if (user.supportsSwitchToByUser()) {
@@ -875,7 +881,7 @@
                                     + (isCurrentUser ? " \u2714" : "")) {
                         public void onPress() {
                             try {
-                                ActivityManager.getService().switchUser(user.id);
+                                mIActivityManager.switchUser(user.id);
                             } catch (RemoteException re) {
                                 Log.e(TAG, "Couldn't switch user " + re);
                             }
@@ -932,7 +938,7 @@
 
     /** {@inheritDoc} */
     public void onShow(DialogInterface dialog) {
-        MetricsLogger.visible(mContext, MetricsEvent.POWER_MENU);
+        mMetricsLogger.visible(MetricsEvent.POWER_MENU);
     }
 
     /**
@@ -1492,7 +1498,7 @@
         if (mHasTelephony) return;
 
         boolean airplaneModeOn = Settings.Global.getInt(
-                mContext.getContentResolver(),
+                mContentResolver,
                 Settings.Global.AIRPLANE_MODE_ON,
                 0) == 1;
         mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off;
@@ -1504,7 +1510,7 @@
      */
     private void changeAirplaneModeSystemSetting(boolean on) {
         Settings.Global.putInt(
-                mContext.getContentResolver(),
+                mContentResolver,
                 Settings.Global.AIRPLANE_MODE_ON,
                 on ? 1 : 0);
         Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index d385123..c911bf2 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -45,24 +45,32 @@
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import javax.inject.Inject;
+
+import dagger.Lazy;
+
 public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks,
         PluginListener<GlobalActionsPanelPlugin> {
 
     private static final float SHUTDOWN_SCRIM_ALPHA = 0.95f;
 
     private final Context mContext;
+    private final Lazy<GlobalActionsDialog> mGlobalActionsDialogLazy;
     private final KeyguardStateController mKeyguardStateController;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final ExtensionController.Extension<GlobalActionsPanelPlugin> mPanelExtension;
     private GlobalActionsPanelPlugin mPlugin;
     private final CommandQueue mCommandQueue;
-    private GlobalActionsDialog mGlobalActions;
+    private GlobalActionsDialog mGlobalActionsDialog;
     private boolean mDisabled;
     private final PluginManager mPluginManager;
     private final String mPluginPackageName;
 
-    public GlobalActionsImpl(Context context, CommandQueue commandQueue) {
+    @Inject
+    public GlobalActionsImpl(Context context, CommandQueue commandQueue,
+            Lazy<GlobalActionsDialog> globalActionsDialogLazy) {
         mContext = context;
+        mGlobalActionsDialogLazy = globalActionsDialogLazy;
         mKeyguardStateController = Dependency.get(KeyguardStateController.class);
         mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
         mPluginManager = Dependency.get(PluginManager.class);
@@ -83,19 +91,17 @@
         mCommandQueue.removeCallback(this);
         mPluginManager.removePluginListener(this);
         if (mPlugin != null) mPlugin.onDestroy();
-        if (mGlobalActions != null) {
-            mGlobalActions.destroy();
-            mGlobalActions = null;
+        if (mGlobalActionsDialog != null) {
+            mGlobalActionsDialog.destroy();
+            mGlobalActionsDialog = null;
         }
     }
 
     @Override
     public void showGlobalActions(GlobalActionsManager manager) {
         if (mDisabled) return;
-        if (mGlobalActions == null) {
-            mGlobalActions = new GlobalActionsDialog(mContext, manager);
-        }
-        mGlobalActions.showDialog(mKeyguardStateController.isShowing(),
+        mGlobalActionsDialog = mGlobalActionsDialogLazy.get();
+        mGlobalActionsDialog.showDialog(mKeyguardStateController.isShowing(),
                 mDeviceProvisionedController.isDeviceProvisioned(),
                 mPlugin != null ? mPlugin : mPanelExtension.get());
         Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth();
@@ -189,8 +195,8 @@
         final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0;
         if (displayId != mContext.getDisplayId() || disabled == mDisabled) return;
         mDisabled = disabled;
-        if (disabled && mGlobalActions != null) {
-            mGlobalActions.dismissDialog();
+        if (disabled && mGlobalActionsDialog != null) {
+            mGlobalActionsDialog.dismissDialog();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 59ac329..48750fa 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -233,7 +233,7 @@
             filter.addAction(Intent.ACTION_SCREEN_OFF);
             filter.addAction(Intent.ACTION_SCREEN_ON);
             filter.addAction(Intent.ACTION_USER_SWITCHED);
-            mBroadcastDispatcher.registerReceiver(this, filter, mHandler);
+            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
index f710f7f..f66a1ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
@@ -68,7 +68,7 @@
     override fun updateResources(): Boolean {
         with(mContext.resources) {
             smallTileSize = getDimensionPixelSize(R.dimen.qs_quick_tile_size)
-            cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal)
+            cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2
             cellMarginVertical = getDimensionPixelSize(R.dimen.new_qs_vertical_margin)
         }
         requestLayout()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
index f7e4c79..1077834e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
@@ -124,7 +124,7 @@
                     }
                 }
             });
-            btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_replay));
+            btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
             btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
             btn.setVisibility(View.VISIBLE);
 
@@ -199,8 +199,7 @@
         List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser());
         if (info != null) {
             for (ResolveInfo inf : info) {
-                if (inf.activityInfo.packageName.equals(notif.contentIntent.getCreatorPackage())) {
-                    Log.d(TAG, "Found receiver for package: " + inf);
+                if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
                     mRecvComponent = inf.getComponentInfo().getComponentName();
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 51e352b..35b8312 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -184,21 +184,10 @@
 
         // Add media carousel
         if (useQsMediaPlayer(context)) {
-            HorizontalScrollView mediaScrollView = new HorizontalScrollView(mContext);
-            mediaScrollView.setHorizontalScrollBarEnabled(false);
-            int playerHeight = (int) getResources().getDimension(R.dimen.qs_media_height);
-            int padding = (int) getResources().getDimension(R.dimen.qs_media_padding);
-            LayoutParams lpView = new LayoutParams(LayoutParams.MATCH_PARENT, playerHeight);
-            lpView.setMarginStart(padding);
-            lpView.setMarginEnd(padding);
-            addView(mediaScrollView, lpView);
-
-            LayoutParams lpCarousel = new LayoutParams(LayoutParams.MATCH_PARENT,
-                    LayoutParams.WRAP_CONTENT);
-            mMediaCarousel = new LinearLayout(mContext);
-            mMediaCarousel.setOrientation(LinearLayout.HORIZONTAL);
-            mediaScrollView.addView(mMediaCarousel, lpCarousel);
-            mediaScrollView.setVisibility(View.GONE);
+            HorizontalScrollView mediaScrollView = (HorizontalScrollView) LayoutInflater.from(
+                    mContext).inflate(R.layout.media_carousel, this, false);
+            mMediaCarousel = mediaScrollView.findViewById(R.id.media_carousel);
+            addView(mediaScrollView);
         } else {
             mMediaCarousel = null;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
index d40e250..cec1cb2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
@@ -106,7 +106,7 @@
                     }
                 }
             });
-            btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_replay));
+            btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
             btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
             btn.setVisibility(View.VISIBLE);
         }
@@ -136,14 +136,25 @@
      * @param actionsContainer a LinearLayout containing the media action buttons
      * @param actionsToShow indices of which actions to display in the mini player
      *                      (max 3: Notification.MediaStyle.MAX_MEDIA_BUTTONS_IN_COMPACT)
+     * @param contentIntent Intent to send when user taps on the view
      */
     public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
-            View actionsContainer, int[] actionsToShow) {
-        Log.d(TAG, "Setting media session: " + token);
+            View actionsContainer, int[] actionsToShow, PendingIntent contentIntent) {
         mToken = token;
         mForegroundColor = iconColor;
         mBackgroundColor = bgColor;
-        mController = new MediaController(mContext, token);
+
+        String oldPackage = "";
+        if (mController != null) {
+            oldPackage = mController.getPackageName();
+        }
+        MediaController controller = new MediaController(mContext, token);
+        boolean samePlayer = mToken.equals(token) && oldPackage.equals(controller.getPackageName());
+        if (mController != null && !samePlayer && !isPlaying(controller)) {
+            // Only update if this is a different session and currently playing
+            return;
+        }
+        mController = controller;
         MediaMetadata mMediaMetadata = mController.getMetadata();
 
         // Try to find a receiver for the media button that matches this app
@@ -153,7 +164,6 @@
         if (info != null) {
             for (ResolveInfo inf : info) {
                 if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
-                    Log.d(TAG, "Found receiver for package: " + inf);
                     mRecvComponent = inf.getComponentInfo().getComponentName();
                 }
             }
@@ -165,6 +175,16 @@
             return;
         }
 
+        // Action
+        mMediaNotifView.setOnClickListener(v -> {
+            try {
+                contentIntent.send();
+                mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+            } catch (PendingIntent.CanceledException e) {
+                Log.e(TAG, "Pending intent was canceled: " + e.getMessage());
+            }
+        });
+
         // Album art
         addAlbumArtBackground(mMediaMetadata, mBackgroundColor);
 
@@ -237,12 +257,12 @@
      * Check whether the media controlled by this player is currently playing
      * @return whether it is playing, or false if no controller information
      */
-    public boolean isPlaying() {
-        if (mController == null) {
+    public boolean isPlaying(MediaController controller) {
+        if (controller == null) {
             return false;
         }
 
-        PlaybackState state = mController.getPlaybackState();
+        PlaybackState state = controller.getPlaybackState();
         if (state == null) {
             return false;
         }
@@ -261,12 +281,11 @@
     private void addAlbumArtBackground(MediaMetadata metadata, int bgColor) {
         Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
         float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
-        if (albumArt != null) {
-            Rect bounds = new Rect();
-            mMediaNotifView.getBoundsOnScreen(bounds);
-            int width = bounds.width();
-            int height = bounds.height();
-
+        Rect bounds = new Rect();
+        mMediaNotifView.getBoundsOnScreen(bounds);
+        int width = bounds.width();
+        int height = bounds.height();
+        if (albumArt != null && width > 0 && height > 0) {
             Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
             Bitmap scaled = scaleBitmap(original, width, height);
             Canvas canvas = new Canvas(scaled);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index db52e7d..b05d4fd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -85,20 +85,19 @@
             mHorizontalLinearLayout.setClipChildren(false);
             mHorizontalLinearLayout.setClipToPadding(false);
 
-            LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
-
             mTileLayout = new DoubleLineTileLayout(context);
             mMediaTileLayout = mTileLayout;
             mRegularTileLayout = new HeaderTileLayout(context);
+            LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
             lp.setMarginEnd(10);
             lp.setMarginStart(0);
             mHorizontalLinearLayout.addView((View) mTileLayout, lp);
 
             mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout);
-
-            lp.setMarginEnd(0);
-            lp.setMarginStart(10);
-            mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp);
+            LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
+            lp2.setMarginEnd(0);
+            lp2.setMarginStart(25);
+            mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp2);
 
             sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index bbff117..ad79cad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -322,7 +322,7 @@
         filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
         try {
             mUserReceiverRegistered.set(true);
-            mBroadcastDispatcher.registerReceiver(this, filter, mHandler, mUser);
+            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler, mUser);
         } catch (Exception ex) {
             mUserReceiverRegistered.set(false);
             Log.e(TAG, "Could not register unlock receiver", ex);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 02c4beb..91d5457 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -16,11 +16,9 @@
 
 package com.android.systemui.screenshot;
 
-import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
 import static android.view.View.VISIBLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW;
 import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;
 
 import android.animation.Animator;
@@ -50,7 +48,6 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.UserHandle;
-import android.provider.DeviceConfig;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Slog;
@@ -246,20 +243,13 @@
             mSaveInBgTask.cancel(false);
         }
 
-        if (!DeviceConfig.getBoolean(
-                NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false)) {
-            mNotificationsController.reset();
-            mNotificationsController.setImage(mScreenBitmap);
-            mNotificationsController.showSavingScreenshotNotification();
-        }
         mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute();
     }
 
     /**
      * Takes a screenshot of the current display and shows an animation.
      */
-    private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible,
-            boolean navBarVisible, Rect crop) {
+    private void takeScreenshot(Consumer<Uri> finisher, Rect crop) {
         int rot = mDisplay.getRotation();
         int width = crop.width();
         int height = crop.height();
@@ -278,21 +268,20 @@
         mScreenBitmap.prepareToDraw();
 
         // Start the post-screenshot animation
-        startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
-                statusBarVisible, navBarVisible);
+        startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels);
     }
 
-    void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) {
+    void takeScreenshot(Consumer<Uri> finisher) {
         mDisplay.getRealMetrics(mDisplayMetrics);
-        takeScreenshot(finisher, statusBarVisible, navBarVisible,
+        takeScreenshot(
+                finisher,
                 new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
     }
 
     /**
      * Displays a screenshot selector
      */
-    void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible,
-            final boolean navBarVisible) {
+    void takeScreenshotPartial(final Consumer<Uri> finisher) {
         mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
         mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
             @Override
@@ -312,8 +301,7 @@
                         if (rect != null) {
                             if (rect.width() != 0 && rect.height() != 0) {
                                 // Need mScreenshotLayout to handle it after the view disappears
-                                mScreenshotLayout.post(() -> takeScreenshot(
-                                        finisher, statusBarVisible, navBarVisible, rect));
+                                mScreenshotLayout.post(() -> takeScreenshot(finisher, rect));
                             }
                         }
 
@@ -364,8 +352,7 @@
     /**
      * Starts the animation after taking the screenshot
      */
-    private void startAnimation(final Consumer<Uri> finisher, int w, int h,
-            boolean statusBarVisible, boolean navBarVisible) {
+    private void startAnimation(final Consumer<Uri> finisher, int w, int h) {
         // If power save is on, show a toast so there is some visual indication that a screenshot
         // has been taken.
         PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -385,50 +372,30 @@
             mScreenshotAnimation.removeAllListeners();
         }
 
-        boolean useCornerFlow =
-                DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false);
         mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
         ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
-        ValueAnimator screenshotFadeOutAnim = useCornerFlow
-                ? createScreenshotToCornerAnimation(w, h)
-                : createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible);
+        ValueAnimator screenshotFadeOutAnim = createScreenshotToCornerAnimation(w, h);
         mScreenshotAnimation = new AnimatorSet();
         mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
         mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
                 // Save the screenshot once we have a bit of time now
-                if (!useCornerFlow) {
-                    saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
-                        @Override
-                        void onActionsReady(Uri uri, List<Notification.Action> actions) {
-                            if (uri == null) {
-                                mNotificationsController.notifyScreenshotError(
-                                        R.string.screenshot_failed_to_capture_text);
-                            } else {
-                                mNotificationsController
-                                        .showScreenshotActionsNotification(uri, actions);
-                            }
+                saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
+                    @Override
+                    void onActionsReady(Uri uri, List<Notification.Action> actions) {
+                        if (uri == null) {
+                            mNotificationsController.notifyScreenshotError(
+                                    R.string.screenshot_failed_to_capture_text);
+                        } else {
+                            mScreenshotHandler.post(() ->
+                                    createScreenshotActionsShadeAnimation(actions).start());
                         }
-                    });
-                    clearScreenshot();
-                } else {
-                    saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
-                        @Override
-                        void onActionsReady(Uri uri, List<Notification.Action> actions) {
-                            if (uri == null) {
-                                mNotificationsController.notifyScreenshotError(
-                                        R.string.screenshot_failed_to_capture_text);
-                            } else {
-                                mScreenshotHandler.post(() ->
-                                        createScreenshotActionsShadeAnimation(actions).start());
-                            }
-                        }
-                    });
-                    mScreenshotHandler.sendMessageDelayed(
-                            mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
-                            SCREENSHOT_CORNER_TIMEOUT_MILLIS);
-                }
+                    }
+                });
+                mScreenshotHandler.sendMessageDelayed(
+                        mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
+                        SCREENSHOT_CORNER_TIMEOUT_MILLIS);
             }
         });
         mScreenshotHandler.post(() -> {
@@ -492,7 +459,7 @@
             }
 
             @Override
-            public void onAnimationEnd(android.animation.Animator animation) {
+            public void onAnimationEnd(Animator animation) {
                 mScreenshotFlash.setVisibility(View.GONE);
             }
         });
@@ -513,81 +480,6 @@
         return anim;
     }
 
-    private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
-            boolean navBarVisible) {
-        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
-        anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mBackgroundView.setVisibility(View.GONE);
-                mScreenshotView.setVisibility(View.GONE);
-                mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
-            }
-        });
-
-        if (!statusBarVisible || !navBarVisible) {
-            // There is no status bar/nav bar, so just fade the screenshot away in place
-            anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
-            anim.addUpdateListener(new AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    float t = (Float) animation.getAnimatedValue();
-                    float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
-                            - t * (SCREENSHOT_DROP_IN_MIN_SCALE
-                            - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
-                    mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
-                    mScreenshotView.setAlpha(1f - t);
-                    mScreenshotView.setScaleX(scaleT);
-                    mScreenshotView.setScaleY(scaleT);
-                }
-            });
-        } else {
-            // In the case where there is a status bar, animate to the origin of the bar (top-left)
-            final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
-                    / SCREENSHOT_DROP_OUT_DURATION;
-            final Interpolator scaleInterpolator = new Interpolator() {
-                @Override
-                public float getInterpolation(float x) {
-                    if (x < scaleDurationPct) {
-                        // Decelerate, and scale the input accordingly
-                        return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
-                    }
-                    return 1f;
-                }
-            };
-
-            // Determine the bounds of how to scale
-            float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
-            float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
-            final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
-            final PointF finalPos = new PointF(
-                    -halfScreenWidth
-                            + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
-                    -halfScreenHeight
-                            + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
-
-            // Animate the screenshot to the status bar
-            anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
-            anim.addUpdateListener(new AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    float t = (Float) animation.getAnimatedValue();
-                    float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
-                            - scaleInterpolator.getInterpolation(t)
-                            * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
-                    mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
-                    mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
-                    mScreenshotView.setScaleX(scaleT);
-                    mScreenshotView.setScaleY(scaleT);
-                    mScreenshotView.setTranslationX(t * finalPos.x);
-                    mScreenshotView.setTranslationY(t * finalPos.y);
-                }
-            });
-        }
-        return anim;
-    }
-
     private ValueAnimator createScreenshotToCornerAnimation(int w, int h) {
         ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
         anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
@@ -776,8 +668,7 @@
             String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE);
             Slog.d(TAG, "Executing smart action [" + actionType + "]:" + actionIntent);
             ActivityOptions opts = ActivityOptions.makeBasic();
-            context.startActivityAsUser(actionIntent, opts.toBundle(),
-                    UserHandle.CURRENT);
+            context.startActivityAsUser(actionIntent, opts.toBundle(), UserHandle.CURRENT);
 
             ScreenshotSmartActions.notifyScreenshotAction(
                     context, intent.getStringExtra(EXTRA_ID), actionType, true);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
new file mode 100644
index 0000000..11aa80b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.media.MediaActionSound;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.PowerManager;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Class for handling device screen shots
+ *
+ * @deprecated will be removed when corner flow is complete and tested
+ */
+@Singleton
+@Deprecated
+public class GlobalScreenshotLegacy {
+
+    // These strings are used for communicating the action invoked to
+    // ScreenshotNotificationSmartActionsProvider.
+    static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
+    static final String EXTRA_ID = "android:screenshot_id";
+    static final String ACTION_TYPE_DELETE = "Delete";
+    static final String ACTION_TYPE_SHARE = "Share";
+    static final String ACTION_TYPE_EDIT = "Edit";
+    static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
+    static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
+
+    static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
+    static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
+    static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
+
+    private static final String TAG = "GlobalScreenshot";
+
+    private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
+    private static final int SCREENSHOT_DROP_IN_DURATION = 430;
+    private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
+    private static final int SCREENSHOT_DROP_OUT_DURATION = 430;
+    private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370;
+    private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320;
+    private static final float BACKGROUND_ALPHA = 0.5f;
+    private static final float SCREENSHOT_SCALE = 1f;
+    private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f;
+    private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f;
+    private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f;
+    private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f;
+
+    private final ScreenshotNotificationsController mNotificationsController;
+
+    private Context mContext;
+    private WindowManager mWindowManager;
+    private WindowManager.LayoutParams mWindowLayoutParams;
+    private Display mDisplay;
+    private DisplayMetrics mDisplayMetrics;
+
+    private Bitmap mScreenBitmap;
+    private View mScreenshotLayout;
+    private ScreenshotSelectorView mScreenshotSelectorView;
+    private ImageView mBackgroundView;
+    private ImageView mScreenshotView;
+    private ImageView mScreenshotFlash;
+
+    private AnimatorSet mScreenshotAnimation;
+
+    private float mBgPadding;
+    private float mBgPaddingScale;
+
+    private AsyncTask<Void, Void, Void> mSaveInBgTask;
+
+    private MediaActionSound mCameraSound;
+
+    /**
+     * @param context everything needs a context :(
+     */
+    @Inject
+    public GlobalScreenshotLegacy(
+            Context context, @Main Resources resources, LayoutInflater layoutInflater,
+            ScreenshotNotificationsController screenshotNotificationsController) {
+        mContext = context;
+        mNotificationsController = screenshotNotificationsController;
+
+        // Inflate the screenshot layout
+        mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot_legacy, null);
+        mBackgroundView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_background);
+        mScreenshotView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy);
+
+        mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_flash);
+        mScreenshotSelectorView = mScreenshotLayout.findViewById(
+                R.id.global_screenshot_legacy_selector);
+        mScreenshotLayout.setFocusable(true);
+        mScreenshotSelectorView.setFocusable(true);
+        mScreenshotSelectorView.setFocusableInTouchMode(true);
+        mScreenshotLayout.setOnTouchListener((v, event) -> {
+            // Intercept and ignore all touch events
+            return true;
+        });
+
+        // Setup the window that we are going to use
+        mWindowLayoutParams = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
+                WindowManager.LayoutParams.TYPE_SCREENSHOT,
+                WindowManager.LayoutParams.FLAG_FULLSCREEN
+                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
+                PixelFormat.TRANSLUCENT);
+        mWindowLayoutParams.setTitle("ScreenshotAnimation");
+        mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        mWindowLayoutParams.setFitWindowInsetsTypes(0 /* types */);
+        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        mDisplay = mWindowManager.getDefaultDisplay();
+        mDisplayMetrics = new DisplayMetrics();
+        mDisplay.getRealMetrics(mDisplayMetrics);
+
+        // Scale has to account for both sides of the bg
+        mBgPadding = (float) resources.getDimensionPixelSize(
+                R.dimen.global_screenshot_legacy_bg_padding);
+        mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels;
+
+
+        // Setup the Camera shutter sound
+        mCameraSound = new MediaActionSound();
+        mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
+    }
+
+    /**
+     * Creates a new worker thread and saves the screenshot to the media store.
+     */
+    private void saveScreenshotInWorkerThread(
+            Consumer<Uri> finisher,
+            @Nullable GlobalScreenshot.ActionsReadyListener actionsReadyListener) {
+        GlobalScreenshot.SaveImageInBackgroundData data =
+                new GlobalScreenshot.SaveImageInBackgroundData();
+        data.image = mScreenBitmap;
+        data.finisher = finisher;
+        data.mActionsReadyListener = actionsReadyListener;
+        if (mSaveInBgTask != null) {
+            mSaveInBgTask.cancel(false);
+        }
+
+        mNotificationsController.reset();
+        mNotificationsController.setImage(mScreenBitmap);
+        mNotificationsController.showSavingScreenshotNotification();
+
+        mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute();
+    }
+
+    /**
+     * Takes a screenshot of the current display and shows an animation.
+     */
+    private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible,
+            boolean navBarVisible, Rect crop) {
+        int rot = mDisplay.getRotation();
+        int width = crop.width();
+        int height = crop.height();
+
+        // Take the screenshot
+        mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
+        if (mScreenBitmap == null) {
+            mNotificationsController.notifyScreenshotError(
+                    R.string.screenshot_failed_to_capture_text);
+            finisher.accept(null);
+            return;
+        }
+
+        // Optimizations
+        mScreenBitmap.setHasAlpha(false);
+        mScreenBitmap.prepareToDraw();
+
+        // Start the post-screenshot animation
+        startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
+                statusBarVisible, navBarVisible);
+    }
+
+    void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) {
+        mDisplay.getRealMetrics(mDisplayMetrics);
+        takeScreenshot(finisher, statusBarVisible, navBarVisible,
+                new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
+    }
+
+    /**
+     * Displays a screenshot selector
+     */
+    void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible,
+            final boolean navBarVisible) {
+        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+        mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                ScreenshotSelectorView view = (ScreenshotSelectorView) v;
+                switch (event.getAction()) {
+                    case MotionEvent.ACTION_DOWN:
+                        view.startSelection((int) event.getX(), (int) event.getY());
+                        return true;
+                    case MotionEvent.ACTION_MOVE:
+                        view.updateSelection((int) event.getX(), (int) event.getY());
+                        return true;
+                    case MotionEvent.ACTION_UP:
+                        view.setVisibility(View.GONE);
+                        mWindowManager.removeView(mScreenshotLayout);
+                        final Rect rect = view.getSelectionRect();
+                        if (rect != null) {
+                            if (rect.width() != 0 && rect.height() != 0) {
+                                // Need mScreenshotLayout to handle it after the view disappears
+                                mScreenshotLayout.post(() -> takeScreenshot(
+                                        finisher, statusBarVisible, navBarVisible, rect));
+                            }
+                        }
+
+                        view.stopSelection();
+                        return true;
+                }
+
+                return false;
+            }
+        });
+        mScreenshotLayout.post(new Runnable() {
+            @Override
+            public void run() {
+                mScreenshotSelectorView.setVisibility(View.VISIBLE);
+                mScreenshotSelectorView.requestFocus();
+            }
+        });
+    }
+
+    /**
+     * Cancels screenshot request
+     */
+    void stopScreenshot() {
+        // If the selector layer still presents on screen, we remove it and resets its state.
+        if (mScreenshotSelectorView.getSelectionRect() != null) {
+            mWindowManager.removeView(mScreenshotLayout);
+            mScreenshotSelectorView.stopSelection();
+        }
+    }
+
+    /**
+     * Clears current screenshot
+     */
+    private void clearScreenshot() {
+        if (mScreenshotLayout.isAttachedToWindow()) {
+            mWindowManager.removeView(mScreenshotLayout);
+        }
+
+        // Clear any references to the bitmap
+        mScreenBitmap = null;
+        mScreenshotView.setImageBitmap(null);
+        mBackgroundView.setVisibility(View.GONE);
+        mScreenshotView.setVisibility(View.GONE);
+        mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
+    }
+
+    /**
+     * Starts the animation after taking the screenshot
+     */
+    private void startAnimation(final Consumer<Uri> finisher, int w, int h,
+            boolean statusBarVisible, boolean navBarVisible) {
+        // If power save is on, show a toast so there is some visual indication that a screenshot
+        // has been taken.
+        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        if (powerManager.isPowerSaveMode()) {
+            Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
+        }
+
+        // Add the view for the animation
+        mScreenshotView.setImageBitmap(mScreenBitmap);
+        mScreenshotLayout.requestFocus();
+
+        // Setup the animation with the screenshot just taken
+        if (mScreenshotAnimation != null) {
+            if (mScreenshotAnimation.isStarted()) {
+                mScreenshotAnimation.end();
+            }
+            mScreenshotAnimation.removeAllListeners();
+        }
+
+        mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+        ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
+        ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,
+                statusBarVisible, navBarVisible);
+        mScreenshotAnimation = new AnimatorSet();
+        mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
+        mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                // Save the screenshot once we have a bit of time now
+                saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() {
+                    @Override
+                    void onActionsReady(Uri uri, List<Notification.Action> actions) {
+                        if (uri == null) {
+                            mNotificationsController.notifyScreenshotError(
+                                    R.string.screenshot_failed_to_capture_text);
+                        } else {
+                            mNotificationsController.showScreenshotActionsNotification(
+                                    uri, actions);
+                        }
+                    }
+                });
+                clearScreenshot();
+            }
+        });
+        mScreenshotLayout.post(() -> {
+            // Play the shutter sound to notify that we've taken a screenshot
+            mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+
+            mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            mScreenshotView.buildLayer();
+            mScreenshotAnimation.start();
+        });
+    }
+
+    private ValueAnimator createScreenshotDropInAnimation() {
+        final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
+                / SCREENSHOT_DROP_IN_DURATION);
+        final float flashDurationPct = 2f * flashPeakDurationPct;
+        final Interpolator flashAlphaInterpolator = new Interpolator() {
+            @Override
+            public float getInterpolation(float x) {
+                // Flash the flash view in and out quickly
+                if (x <= flashDurationPct) {
+                    return (float) Math.sin(Math.PI * (x / flashDurationPct));
+                }
+                return 0;
+            }
+        };
+        final Interpolator scaleInterpolator = new Interpolator() {
+            @Override
+            public float getInterpolation(float x) {
+                // We start scaling when the flash is at it's peak
+                if (x < flashPeakDurationPct) {
+                    return 0;
+                }
+                return (x - flashDurationPct) / (1f - flashDurationPct);
+            }
+        };
+
+        Resources r = mContext.getResources();
+        if ((r.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                == Configuration.UI_MODE_NIGHT_YES) {
+            mScreenshotView.getBackground().setTint(Color.BLACK);
+        } else {
+            mScreenshotView.getBackground().setTintList(null);
+        }
+
+        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+        anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mBackgroundView.setAlpha(0f);
+                mBackgroundView.setVisibility(View.VISIBLE);
+                mScreenshotView.setAlpha(0f);
+                mScreenshotView.setTranslationX(0f);
+                mScreenshotView.setTranslationY(0f);
+                mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
+                mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
+                mScreenshotView.setVisibility(View.VISIBLE);
+                mScreenshotFlash.setAlpha(0f);
+                mScreenshotFlash.setVisibility(View.VISIBLE);
+            }
+
+            @Override
+            public void onAnimationEnd(android.animation.Animator animation) {
+                mScreenshotFlash.setVisibility(View.GONE);
+            }
+        });
+        anim.addUpdateListener(new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                float t = (Float) animation.getAnimatedValue();
+                float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
+                        - scaleInterpolator.getInterpolation(t)
+                        * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
+                mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
+                mScreenshotView.setAlpha(t);
+                mScreenshotView.setScaleX(scaleT);
+                mScreenshotView.setScaleY(scaleT);
+                mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
+            }
+        });
+        return anim;
+    }
+
+    private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
+            boolean navBarVisible) {
+        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+        anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mBackgroundView.setVisibility(View.GONE);
+                mScreenshotView.setVisibility(View.GONE);
+                mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
+            }
+        });
+
+        if (!statusBarVisible || !navBarVisible) {
+            // There is no status bar/nav bar, so just fade the screenshot away in place
+            anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
+            anim.addUpdateListener(new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    float t = (Float) animation.getAnimatedValue();
+                    float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
+                            - t * (SCREENSHOT_DROP_IN_MIN_SCALE
+                            - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
+                    mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
+                    mScreenshotView.setAlpha(1f - t);
+                    mScreenshotView.setScaleX(scaleT);
+                    mScreenshotView.setScaleY(scaleT);
+                }
+            });
+        } else {
+            // In the case where there is a status bar, animate to the origin of the bar (top-left)
+            final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
+                    / SCREENSHOT_DROP_OUT_DURATION;
+            final Interpolator scaleInterpolator = new Interpolator() {
+                @Override
+                public float getInterpolation(float x) {
+                    if (x < scaleDurationPct) {
+                        // Decelerate, and scale the input accordingly
+                        return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
+                    }
+                    return 1f;
+                }
+            };
+
+            // Determine the bounds of how to scale
+            float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
+            float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
+            final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
+            final PointF finalPos = new PointF(
+                    -halfScreenWidth
+                            + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
+                    -halfScreenHeight
+                            + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
+
+            // Animate the screenshot to the status bar
+            anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
+            anim.addUpdateListener(new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    float t = (Float) animation.getAnimatedValue();
+                    float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
+                            - scaleInterpolator.getInterpolation(t)
+                            * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
+                    mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
+                    mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
+                    mScreenshotView.setScaleX(scaleT);
+                    mScreenshotView.setScaleY(scaleT);
+                    mScreenshotView.setTranslationX(t * finalPos.x);
+                    mScreenshotView.setTranslationY(t * finalPos.y);
+                }
+            });
+        }
+        return anim;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 29b96a9..4f045d5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -16,15 +16,21 @@
 
 package com.android.systemui.screenshot;
 
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW;
+
 import android.app.Service;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.provider.DeviceConfig;
 import android.util.Log;
 import android.view.WindowManager;
 
@@ -36,9 +42,10 @@
     private static final String TAG = "TakeScreenshotService";
 
     private final GlobalScreenshot mScreenshot;
+    private final GlobalScreenshotLegacy mScreenshotLegacy;
     private final UserManager mUserManager;
 
-    private Handler mHandler = new Handler() {
+    private Handler mHandler = new Handler(Looper.myLooper()) {
         @Override
         public void handleMessage(Message msg) {
             final Messenger callback = msg.replyTo;
@@ -59,12 +66,24 @@
                 return;
             }
 
+            // TODO (mkephart): clean up once notifications flow is fully deprecated
+            boolean useCornerFlow = DeviceConfig.getBoolean(
+                    NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false);
             switch (msg.what) {
                 case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
-                    mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
+                    if (useCornerFlow) {
+                        mScreenshot.takeScreenshot(finisher);
+                    } else {
+                        mScreenshotLegacy.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
+                    }
                     break;
                 case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
-                    mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0);
+                    if (useCornerFlow) {
+                        mScreenshot.takeScreenshotPartial(finisher);
+                    } else {
+                        mScreenshotLegacy.takeScreenshotPartial(
+                                finisher, msg.arg1 > 0, msg.arg2 > 0);
+                    }
                     break;
                 default:
                     Log.d(TAG, "Invalid screenshot option: " + msg.what);
@@ -73,8 +92,10 @@
     };
 
     @Inject
-    public TakeScreenshotService(GlobalScreenshot globalScreenshot, UserManager userManager) {
+    public TakeScreenshotService(GlobalScreenshot globalScreenshot,
+            GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager) {
         mScreenshot = globalScreenshot;
+        mScreenshotLegacy = globalScreenshotLegacy;
         mUserManager = userManager;
     }
 
@@ -86,6 +107,7 @@
     @Override
     public boolean onUnbind(Intent intent) {
         if (mScreenshot != null) mScreenshot.stopScreenshot();
+        if (mScreenshotLegacy != null) mScreenshotLegacy.stopScreenshot();
         return true;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
index 077d260..9599d77 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
@@ -98,7 +98,8 @@
             if (!mReceiverRegistered) {
                 mCurrentUserId = ActivityManager.getCurrentUser();
                 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
-                mBroadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
+                mBroadcastDispatcher.registerReceiver(this, filter, null,
+                        UserHandle.ALL);
                 mReceiverRegistered = true;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 2005d79..ac05c53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -40,7 +40,7 @@
  * You will probably need to restart systemui for the changes to be picked up:
  *
  * {@code
- *  $ adb shell am crash com.android.systemui
+ *  $ adb shell am restart com.android.systemui
  * }
  */
 @Singleton
@@ -59,6 +59,11 @@
         return getDeviceConfigFlag("notification.newpipeline.enabled", false);
     }
 
+    public boolean isNewNotifPipelineRenderingEnabled() {
+        return isNewNotifPipelineEnabled()
+                && getDeviceConfigFlag("notification.newpipeline.rendering", false);
+    }
+
     private void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
         synchronized (mCachedDeviceConfigFlags) {
             for (String key : properties.getKeyset()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index ff4ce94..ebf7c2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -49,6 +49,12 @@
     /** Adds a listener to be notified when the current user changes. */
     void addUserChangedListener(UserChangedListener listener);
 
+    /**
+     * Removes a listener previously registered with
+     * {@link #addUserChangedListener(UserChangedListener)}
+     */
+    void removeUserChangedListener(UserChangedListener listener);
+
     SparseArray<UserInfo> getCurrentProfiles();
 
     void setLockscreenPublicMode(boolean isProfilePublic, int userId);
@@ -79,6 +85,7 @@
 
     /** Notified when the current user changes. */
     interface UserChangedListener {
-        void onUserChanged(int userId);
+        default void onUserChanged(int userId) {}
+        default void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 2e369b3..976531d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -117,51 +117,63 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                updateCurrentProfilesCache();
-                Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
+            switch (action) {
+                case Intent.ACTION_USER_SWITCHED:
+                    mCurrentUserId = intent.getIntExtra(
+                            Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL);
+                    updateCurrentProfilesCache();
 
-                updateLockscreenNotificationSetting();
-                updatePublicMode();
-                // The filtering needs to happen before the update call below in order to make sure
-                // the presenter has the updated notifications from the new user
-                getEntryManager().reapplyFilterAndSort("user switched");
-                mPresenter.onUserSwitched(mCurrentUserId);
+                    Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
 
-                for (UserChangedListener listener : mListeners) {
-                    listener.onUserChanged(mCurrentUserId);
-                }
-            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
-                updateCurrentProfilesCache();
-            } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
-                // Start the overview connection to the launcher service
-                Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
-            } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) {
-                final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT);
-                final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
-                if (intentSender != null) {
-                    try {
-                        mContext.startIntentSender(intentSender, null, 0, 0, 0);
-                    } catch (IntentSender.SendIntentException e) {
-                        /* ignore */
+                    updateLockscreenNotificationSetting();
+                    updatePublicMode();
+                    // The filtering needs to happen before the update call below in order to
+                    // make sure
+                    // the presenter has the updated notifications from the new user
+                    getEntryManager().reapplyFilterAndSort("user switched");
+                    mPresenter.onUserSwitched(mCurrentUserId);
+
+                    for (UserChangedListener listener : mListeners) {
+                        listener.onUserChanged(mCurrentUserId);
                     }
-                }
-                if (notificationKey != null) {
-                    NotificationEntry entry =
-                            getEntryManager().getActiveNotificationUnfiltered(notificationKey);
-                    final int count = getEntryManager().getActiveNotificationsCount();
-                    final int rank = entry != null ? entry.getRanking().getRank() : 0;
-                    NotificationVisibility.NotificationLocation location =
-                            NotificationLogger.getNotificationLocation(entry);
-                    final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
-                            rank, count, true, location);
-                    try {
-                        mBarService.onNotificationClick(notificationKey, nv);
-                    } catch (RemoteException exception) {
-                        /* ignore */
+                    break;
+                case Intent.ACTION_USER_ADDED:
+                case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
+                case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
+                    updateCurrentProfilesCache();
+                    break;
+                case Intent.ACTION_USER_UNLOCKED:
+                    // Start the overview connection to the launcher service
+                    Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
+                    break;
+                case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION:
+                    final IntentSender intentSender = intent.getParcelableExtra(
+                            Intent.EXTRA_INTENT);
+                    final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
+                    if (intentSender != null) {
+                        try {
+                            mContext.startIntentSender(intentSender, null, 0, 0, 0);
+                        } catch (IntentSender.SendIntentException e) {
+                            /* ignore */
+                        }
                     }
-                }
+                    if (notificationKey != null) {
+                        NotificationEntry entry =
+                                getEntryManager().getActiveNotificationUnfiltered(notificationKey);
+                        final int count = getEntryManager().getActiveNotificationsCount();
+                        final int rank = entry != null ? entry.getRanking().getRank() : 0;
+                        NotificationVisibility.NotificationLocation location =
+                                NotificationLogger.getNotificationLocation(entry);
+                        final NotificationVisibility nv = NotificationVisibility.obtain(
+                                notificationKey,
+                                rank, count, true, location);
+                        try {
+                            mBarService.onNotificationClick(notificationKey, nv);
+                        } catch (RemoteException exception) {
+                            /* ignore */
+                        }
+                    }
+                    break;
             }
         }
     };
@@ -266,6 +278,8 @@
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_USER_ADDED);
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
+        filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
+        filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
         mBroadcastDispatcher.registerReceiver(mBaseBroadcastReceiver, filter);
 
         IntentFilter internalFilter = new IntentFilter();
@@ -489,6 +503,11 @@
                 }
             }
         }
+        mMainHandler.post(() -> {
+            for (UserChangedListener listener : mListeners) {
+                listener.onCurrentProfilesChanged(mCurrentProfiles);
+            }
+        });
     }
 
     public boolean isAnyProfilePublicMode() {
@@ -555,6 +574,11 @@
         mListeners.add(listener);
     }
 
+    @Override
+    public void removeUserChangedListener(UserChangedListener listener) {
+        mListeners.remove(listener);
+    }
+
 //    public void updatePublicMode() {
 //        //TODO: I think there may be a race condition where mKeyguardViewManager.isShowing() returns
 //        // false when it should be true. Therefore, if we are not on the SHADE, don't even bother
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 43d0399..667e721 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -584,7 +584,15 @@
 
     public void bindRow(ExpandableNotificationRow row) {
         row.setRemoteInputController(mRemoteInputController);
-        row.setRemoteViewClickHandler(mOnClickHandler);
+    }
+
+    /**
+     * Return on-click handler for notification remote views
+     *
+     * @return on-click handler
+     */
+    public RemoteViews.OnClickHandler getRemoteViewsOnClickHandler() {
+        return mOnClickHandler;
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
index d1f6ebf..ec8dbea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 
+import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+
 import javax.inject.Singleton;
 
 import dagger.Module;
@@ -26,7 +28,7 @@
 /**
  * Dagger Module providing common dependencies of StatusBar.
  */
-@Module
+@Module(includes = {NotificationRowModule.class})
 public class StatusBarDependenciesModule {
     /**
      * Provides our instance of CommandQueue which is considered optional.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index dc84b57..9c626f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -307,6 +307,7 @@
             case Icon.TYPE_RESOURCE:
                 return a.getResPackage().equals(b.getResPackage()) && a.getResId() == b.getResId();
             case Icon.TYPE_URI:
+            case Icon.TYPE_URI_ADAPTIVE_BITMAP:
                 return a.getUriString().equals(b.getUriString());
             default:
                 return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 33d97a1..36dcaac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -32,8 +32,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
@@ -67,6 +67,8 @@
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
+import dagger.Lazy;
+
 /**
  * NotificationEntryManager is responsible for the adding, removing, and updating of
  * {@link NotificationEntry}s. It also handles tasks such as their inflation and their interaction
@@ -125,12 +127,14 @@
             new ArrayMap<>();
 
     // Lazily retrieved dependencies
-    private NotificationRemoteInputManager mRemoteInputManager;
-    private NotificationRowBinder mNotificationRowBinder;
+    private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy;
+    private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
+    private final LeakDetector mLeakDetector;
 
     private final KeyguardEnvironment mKeyguardEnvironment;
     private final NotificationGroupManager mGroupManager;
     private final NotificationRankingManager mRankingManager;
+    private final FeatureFlags mFeatureFlags;
 
     private NotificationPresenter mPresenter;
     private RankingMap mLatestRankingMap;
@@ -170,11 +174,19 @@
             NotifLog notifLog,
             NotificationGroupManager groupManager,
             NotificationRankingManager rankingManager,
-            KeyguardEnvironment keyguardEnvironment) {
+            KeyguardEnvironment keyguardEnvironment,
+            FeatureFlags featureFlags,
+            Lazy<NotificationRowBinder> notificationRowBinderLazy,
+            Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
+            LeakDetector leakDetector) {
         mNotifLog = notifLog;
         mGroupManager = groupManager;
         mRankingManager = rankingManager;
         mKeyguardEnvironment = keyguardEnvironment;
+        mFeatureFlags = featureFlags;
+        mNotificationRowBinderLazy = notificationRowBinderLazy;
+        mRemoteInputManagerLazy = notificationRemoteInputManagerLazy;
+        mLeakDetector = leakDetector;
     }
 
     /** Once called, the NEM will start processing notification events from system server. */
@@ -200,20 +212,6 @@
         mRemoveInterceptor = interceptor;
     }
 
-    /**
-     * Our dependencies can have cyclic references, so some need to be lazy
-     */
-    private NotificationRemoteInputManager getRemoteInputManager() {
-        if (mRemoteInputManager == null) {
-            mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
-        }
-        return mRemoteInputManager;
-    }
-
-    public void setRowBinder(NotificationRowBinder notificationRowBinder) {
-        mNotificationRowBinder = notificationRowBinder;
-    }
-
     public void setUpWithPresenter(NotificationPresenter presenter,
             NotificationListContainer listContainer,
             HeadsUpManager headsUpManager) {
@@ -290,6 +288,10 @@
      * WARNING: this will call back into us.  Don't hold any locks.
      */
     @Override
+    public void handleInflationException(NotificationEntry n, Exception e) {
+        handleInflationException(n.getSbn(), e);
+    }
+
     public void handleInflationException(StatusBarNotification n, Exception e) {
         removeNotificationInternal(
                 n.getKey(), null, null, true /* forceRemove */, false /* removedByUser */,
@@ -460,7 +462,7 @@
                 handleGroupSummaryRemoved(key);
                 removeVisibleNotification(key);
                 updateNotifications("removeNotificationInternal");
-                Dependency.get(LeakDetector.class).trackGarbage(entry);
+                mLeakDetector.trackGarbage(entry);
                 removedByUser |= entryDismissed;
 
                 mNotifLog.log(NotifEvent.NOTIF_REMOVED, entry.getSbn(),
@@ -499,8 +501,8 @@
                 boolean isForeground = (entry.getSbn().getNotification().flags
                         & Notification.FLAG_FOREGROUND_SERVICE) != 0;
                 boolean keepForReply =
-                        getRemoteInputManager().shouldKeepForRemoteInputHistory(childEntry)
-                        || getRemoteInputManager().shouldKeepForSmartReplyHistory(childEntry);
+                        mRemoteInputManagerLazy.get().shouldKeepForRemoteInputHistory(childEntry)
+                        || mRemoteInputManagerLazy.get().shouldKeepForSmartReplyHistory(childEntry);
                 if (isForeground || keepForReply) {
                     // the child is a foreground service notification which we can't remove or it's
                     // a child we're keeping around for reply!
@@ -528,10 +530,14 @@
 
         NotificationEntry entry = new NotificationEntry(notification, ranking);
 
-        Dependency.get(LeakDetector.class).trackInstance(entry);
+        mLeakDetector.trackInstance(entry);
+
         // Construct the expanded view.
-        requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
-                REASON_CANCEL));
+        if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mNotificationRowBinderLazy.get()
+                    .inflateViews(entry, () -> performRemoveNotification(notification,
+                            REASON_CANCEL));
+        }
 
         abortExistingInflation(key, "addNotification");
         mPendingNotifications.put(key, entry);
@@ -574,13 +580,17 @@
             listener.onPreEntryUpdated(entry);
         }
 
-        requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
-                REASON_CANCEL));
+        if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mNotificationRowBinderLazy.get()
+                    .inflateViews(entry, () -> performRemoveNotification(notification,
+                            REASON_CANCEL));
+        }
+
         updateNotifications("updateNotificationInternal");
 
         if (DEBUG) {
             // Is this for you?
-            boolean isForCurrentUser = Dependency.get(KeyguardEnvironment.class)
+            boolean isForCurrentUser = mKeyguardEnvironment
                     .isNotificationForCurrentProfiles(notification);
             Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
         }
@@ -630,11 +640,12 @@
 
         // By comparing the old and new UI adjustments, reinflate the view accordingly.
         for (NotificationEntry entry : entries) {
-            requireBinder().onNotificationRankingUpdated(
-                    entry,
-                    oldImportances.get(entry.getKey()),
-                    oldAdjustments.get(entry.getKey()),
-                    NotificationUiAdjustment.extractFromNotificationEntry(entry));
+            mNotificationRowBinderLazy.get()
+                    .onNotificationRankingUpdated(
+                            entry,
+                            oldImportances.get(entry.getKey()),
+                            oldAdjustments.get(entry.getKey()),
+                            NotificationUiAdjustment.extractFromNotificationEntry(entry));
         }
 
         updateNotifications("updateNotificationRanking");
@@ -714,14 +725,6 @@
         }
     }
 
-    private NotificationRowBinder requireBinder() {
-        if (mNotificationRowBinder == null) {
-            throw new RuntimeException("You must initialize NotificationEntryManager by calling"
-                    + "setRowBinder() before using.");
-        }
-        return mNotificationRowBinder;
-    }
-
     /*
      * -----
      * Annexed from NotificationData below:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
index ec1efa5..b960b42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
@@ -24,7 +24,6 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
-import java.util.Objects;
 
 /**
  * Represents a set of grouped notifications. The final notification list is usually a mix of
@@ -58,22 +57,15 @@
 
     @VisibleForTesting
     public void setSummary(@Nullable NotificationEntry summary) {
-        if (!Objects.equals(mSummary, summary)) {
-            mSummary = summary;
-            onGroupingUpdated();
-        }
+        mSummary = summary;
     }
 
     void clearChildren() {
-        if (mChildren.size() != 0) {
-            mChildren.clear();
-            onGroupingUpdated();
-        }
+        mChildren.clear();
     }
 
     void addChild(NotificationEntry child) {
         mChildren.add(child);
-        onGroupingUpdated();
     }
 
     void sortChildren(Comparator<? super NotificationEntry> c) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index 601b3e0..dc68c4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -18,38 +18,20 @@
 
 import android.annotation.Nullable;
 
-import com.android.systemui.Dependency;
-import com.android.systemui.statusbar.notification.collection.provider.DerivedMember;
-import com.android.systemui.statusbar.notification.collection.provider.IsHighPriorityProvider;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-
 /**
  * Abstract superclass for top-level entries, i.e. things that can appear in the final notification
  * list shown to users. In practice, this means either GroupEntries or NotificationEntries.
  */
 public abstract class ListEntry {
     private final String mKey;
-    private final IsHighPriorityProvider mIsHighPriorityProvider = new IsHighPriorityProvider();
-    private final List<DerivedMember> mDerivedMemberList = Arrays.asList(mIsHighPriorityProvider);
 
     @Nullable private GroupEntry mParent;
     @Nullable private GroupEntry mPreviousParent;
     private int mSection;
     int mFirstAddedIteration = -1;
 
-    // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic
-    //  replaced in GroupEntry and NotifListBuilderImpl
-    private final NotificationGroupManager mGroupManager;
-
     ListEntry(String key) {
         mKey = key;
-
-        // TODO: (b/145659174) remove
-        mGroupManager = Dependency.get(NotificationGroupManager.class);
     }
 
     public String getKey() {
@@ -68,11 +50,7 @@
     }
 
     void setParent(@Nullable GroupEntry parent) {
-        if (!Objects.equals(mParent, parent)) {
-            invalidateParent();
-            mParent = parent;
-            onGroupingUpdated();
-        }
+        mParent = parent;
     }
 
     @Nullable public GroupEntry getPreviousParent() {
@@ -91,58 +69,4 @@
     void setSection(int section) {
         mSection = section;
     }
-
-    /**
-     * Resets the cached values of DerivedMembers.
-     */
-    void invalidateDerivedMembers() {
-        for (int i = 0; i < mDerivedMemberList.size(); i++) {
-            mDerivedMemberList.get(i).invalidate();
-        }
-    }
-
-    /**
-     * Whether this notification is shown to the user as a high priority notification: visible on
-     * the lock screen/status bar and in the top section in the shade.
-     */
-    public boolean isHighPriority() {
-        return mIsHighPriorityProvider.get(this);
-    }
-
-    private void invalidateParent() {
-        // invalidate our parent (GroupEntry) since DerivedMembers may be dependent on children
-        if (getParent() != null) {
-            getParent().invalidateDerivedMembers();
-        }
-
-        // TODO: (b/145659174) remove
-        final NotificationEntry notifEntry = getRepresentativeEntry();
-        if (notifEntry != null && mGroupManager.isGroupChild(notifEntry.getSbn())) {
-            NotificationEntry summary = mGroupManager.getLogicalGroupSummary(notifEntry.getSbn());
-            if (summary != null) {
-                summary.invalidateDerivedMembers();
-            }
-        }
-    }
-
-    void onGroupingUpdated() {
-        for (int i = 0; i < mDerivedMemberList.size(); i++) {
-            mDerivedMemberList.get(i).onGroupingUpdated();
-        }
-        invalidateParent();
-    }
-
-    void onSbnUpdated() {
-        for (int i = 0; i < mDerivedMemberList.size(); i++) {
-            mDerivedMemberList.get(i).onSbnUpdated();
-        }
-        invalidateParent();
-    }
-
-    void onRankingUpdated() {
-        for (int i = 0; i < mDerivedMemberList.size(); i++) {
-            mDerivedMemberList.get(i).onRankingUpdated();
-        }
-        invalidateParent();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 873cdbc..856b75b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -299,6 +299,14 @@
             if (!isLifetimeExtended(entry)) {
                 Ranking ranking = requireRanking(rankingMap, entry.getKey());
                 entry.setRanking(ranking);
+
+                // TODO: (b/145659174) update the sbn's overrideGroupKey in
+                //  NotificationEntry.setRanking instead of here once we fully migrate to the
+                //  NewNotifPipeline
+                final String newOverrideGroupKey = ranking.getOverrideGroupKey();
+                if (!Objects.equals(entry.getSbn().getOverrideGroupKey(), newOverrideGroupKey)) {
+                    entry.getSbn().setOverrideGroupKey(newOverrideGroupKey);
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java
new file mode 100644
index 0000000..fc04827
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+import com.android.systemui.statusbar.notification.collection.coordinator.PreparationCoordinator;
+
+/**
+ * Used by the {@link PreparationCoordinator}.  When notifications are added or updated, the
+ * NotifInflater is asked to (re)inflated and prepare their views.  This inflation occurs off the
+ * main thread. When the inflation is finished, NotifInflater will trigger its InflationCallback.
+ */
+public interface NotifInflater {
+
+    /**
+     * Callback used when inflation is finished.
+     */
+    void setInflationCallback(InflationCallback callback);
+
+    /**
+     * Called to rebind the entry's views.
+     */
+    void rebindViews(NotificationEntry entry);
+
+    /**
+     * Called to inflate the views of an entry.  Views are not considered inflated until all of its
+     * views are bound. Once all views are inflated, the InflationCallback is triggered.
+     */
+    void inflateViews(NotificationEntry entry);
+
+    /**
+     * Request to stop the inflation of an entry.  For example, called when a notification is
+     * removed and no longer needs to be inflated.
+     */
+    void abortInflation(NotificationEntry entry);
+
+    /**
+     * Callback once all the views are inflated and bound for a given NotificationEntry.
+     */
+    interface InflationCallback {
+        void onInflationFinished(NotificationEntry entry);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
new file mode 100644
index 0000000..0d17557
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
+
+import android.os.RemoteException;
+import android.service.notification.NotificationStats;
+import android.service.notification.StatusBarNotification;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Handles notification inflating, rebinding, and inflation aborting.
+ *
+ * Currently a wrapper for NotificationRowBinderImpl.
+ */
+@Singleton
+public class NotifInflaterImpl implements NotifInflater {
+
+    private final IStatusBarService mStatusBarService;
+    private final NotifCollection mNotifCollection;
+
+    private NotificationRowBinderImpl mNotificationRowBinder;
+    private InflationCallback mExternalInflationCallback;
+
+    @Inject
+    public NotifInflaterImpl(
+            IStatusBarService statusBarService,
+            NotifCollection notifCollection) {
+        mStatusBarService = statusBarService;
+        mNotifCollection = notifCollection;
+    }
+
+    /**
+     * Attaches the row binder for inflation.
+     */
+    public void setRowBinder(NotificationRowBinderImpl rowBinder) {
+        mNotificationRowBinder = rowBinder;
+        mNotificationRowBinder.setInflationCallback(mInflationCallback);
+    }
+
+    @Override
+    public void setInflationCallback(InflationCallback callback) {
+        mExternalInflationCallback = callback;
+    }
+
+    @Override
+    public void rebindViews(NotificationEntry entry) {
+        inflateViews(entry);
+    }
+
+    /**
+     * Called to inflate the views of an entry.  Views are not considered inflated until all of its
+     * views are bound.
+     */
+    @Override
+    public void inflateViews(NotificationEntry entry) {
+        try {
+            entry.setHasInflationError(false);
+            requireBinder().inflateViews(entry, getDismissCallback(entry));
+        } catch (InflationException e) {
+            // logged in mInflationCallback.handleInflationException
+        }
+    }
+
+    @Override
+    public void abortInflation(NotificationEntry entry) {
+        entry.abortTask();
+    }
+
+    private Runnable getDismissCallback(NotificationEntry entry) {
+        return new Runnable() {
+            @Override
+            public void run() {
+                int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+                /**
+                 * TODO: determine dismissal surface (ie: shade / headsup / aod)
+                 * see {@link NotificationLogger#logNotificationClear}
+                 */
+                mNotifCollection.dismissNotification(
+                        entry,
+                        0,
+                        new DismissedByUserStats(
+                                dismissalSurface,
+                                DISMISS_SENTIMENT_NEUTRAL,
+                                NotificationVisibility.obtain(entry.getKey(),
+                                        entry.getRanking().getRank(),
+                                        mNotifCollection.getNotifs().size(),
+                                        true,
+                                        NotificationLogger.getNotificationLocation(entry))
+                        ));
+            }
+        };
+    }
+
+    private NotificationRowBinderImpl requireBinder() {
+        if (mNotificationRowBinder == null) {
+            throw new RuntimeException("NotificationRowBinder must be attached before using "
+                    + "NotifInflaterImpl.");
+        }
+        return mNotificationRowBinder;
+    }
+
+    private final NotificationContentInflater.InflationCallback mInflationCallback =
+            new NotificationContentInflater.InflationCallback() {
+                @Override
+                public void handleInflationException(
+                        NotificationEntry entry,
+                        Exception e) {
+                    entry.setHasInflationError(true);
+                    try {
+                        final StatusBarNotification sbn = entry.getSbn();
+                        // report notification inflation errors back up
+                        // to notification delegates
+                        mStatusBarService.onNotificationError(
+                                sbn.getPackageName(),
+                                sbn.getTag(),
+                                sbn.getId(),
+                                sbn.getUid(),
+                                sbn.getInitialPid(),
+                                e.getMessage(),
+                                sbn.getUserId());
+                    } catch (RemoteException ex) {
+                    }
+                }
+
+                @Override
+                public void onAsyncInflationFinished(
+                        NotificationEntry entry,
+                        int inflatedFlags) {
+                    if (mExternalInflationCallback != null) {
+                        mExternalInflationCallback.onInflationFinished(entry);
+                    }
+                }
+            };
+}
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 4f4fb24..7301fe1 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
@@ -102,6 +102,9 @@
     /** If this was a group child that was promoted to the top level, then who did the promoting. */
     @Nullable NotifPromoter mNotifPromoter;
 
+    /** If this notification had an issue with inflating. Only used with the NewNotifPipeline **/
+    private boolean mHasInflationError;
+
 
     /*
     * Old members
@@ -201,11 +204,8 @@
                     + " doesn't match existing key " + mKey);
         }
 
-        if (!Objects.equals(mSbn, sbn)) {
-            mSbn = sbn;
-            mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
-            onSbnUpdated();
-        }
+        mSbn = sbn;
+        mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
     }
 
     /**
@@ -230,10 +230,7 @@
                     + " doesn't match existing key " + mKey);
         }
 
-        if (!Objects.equals(mRanking, ranking)) {
-            mRanking = ranking;
-            onRankingUpdated();
-        }
+        mRanking = ranking;
     }
 
     /*
@@ -576,6 +573,18 @@
         remoteInputTextWhenReset = null;
     }
 
+    void setHasInflationError(boolean hasError) {
+        mHasInflationError = hasError;
+    }
+
+    /**
+     * Whether this notification had an error when attempting to inflate. This is only used in
+     * the NewNotifPipeline
+     */
+    public boolean hasInflationError() {
+        return mHasInflationError;
+    }
+
     public void setHasSentReply() {
         hasSentReply = true;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index 7010943..3bbd722 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.notification.NotificationFilter
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
 import com.android.systemui.statusbar.notification.logging.NotifEvent
 import com.android.systemui.statusbar.notification.logging.NotifLog
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
@@ -54,7 +55,8 @@
     private val notifFilter: NotificationFilter,
     private val notifLog: NotifLog,
     sectionsFeatureManager: NotificationSectionsFeatureManager,
-    private val peopleNotificationIdentifier: PeopleNotificationIdentifier
+    private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
+    private val highPriorityProvider: HighPriorityProvider
 ) {
 
     var rankingMap: RankingMap? = null
@@ -81,6 +83,9 @@
         val aHeadsUp = a.isRowHeadsUp
         val bHeadsUp = b.isRowHeadsUp
 
+        val aIsHighPriority = a.isHighPriority()
+        val bIsHighPriority = b.isHighPriority()
+
         when {
             usePeopleFiltering && aIsPeople != bIsPeople -> if (aIsPeople) -1 else 1
             aHeadsUp != bHeadsUp -> if (aHeadsUp) -1 else 1
@@ -90,8 +95,8 @@
             aMedia != bMedia -> if (aMedia) -1 else 1
             // Upsort PRIORITY_MAX system notifications
             aSystemMax != bSystemMax -> if (aSystemMax) -1 else 1
-            a.isHighPriority != b.isHighPriority ->
-                -1 * a.isHighPriority.compareTo(b.isHighPriority)
+            aIsHighPriority != bIsHighPriority ->
+                -1 * aIsHighPriority.compareTo(bIsHighPriority)
             aRank != bRank -> aRank - bRank
             else -> nb.notification.`when`.compareTo(na.notification.`when`)
         }
@@ -154,7 +159,7 @@
     ) {
         if (usePeopleFiltering && entry.isPeopleNotification()) {
             entry.bucket = BUCKET_PEOPLE
-        } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority) {
+        } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority()) {
             entry.bucket = BUCKET_ALERTING
         } else {
             entry.bucket = BUCKET_SILENT
@@ -178,10 +183,6 @@
                         // TODO: notify group manager here?
                         groupManager.onEntryUpdated(entry, oldSbn)
                     }
-
-                    // TODO: (b/145659174) remove after moving to new NotifPipeline
-                    // (should be able to remove all groupManager code post-migration)
-                    entry.invalidateDerivedMembers()
                 }
             }
         }
@@ -191,6 +192,9 @@
             sbn.isPeopleNotification()
     private fun StatusBarNotification.isPeopleNotification() =
             peopleNotificationIdentifier.isPeopleNotification(this)
+
+    private fun NotificationEntry.isHighPriority() =
+            highPriorityProvider.isHighPriority(this)
 }
 
 // Convenience functions
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 6c93618..80b5b8a 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection;
 
+import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 
@@ -29,7 +30,6 @@
 import android.view.ViewGroup;
 
 import com.android.internal.util.NotificationMessagingUtil;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -41,8 +41,8 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -52,73 +52,85 @@
 
 import java.util.Objects;
 
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
 /** Handles inflating and updating views for notifications. */
+@Singleton
 public class NotificationRowBinderImpl implements NotificationRowBinder {
 
     private static final String TAG = "NotificationViewManager";
 
-    private final NotificationGroupManager mGroupManager =
-            Dependency.get(NotificationGroupManager.class);
-    private final NotificationGutsManager mGutsManager =
-            Dependency.get(NotificationGutsManager.class);
-    private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
-            Dependency.get(NotificationInterruptionStateProvider.class);
+    private final NotificationGroupManager mGroupManager;
+    private final NotificationGutsManager mGutsManager;
+    private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
 
     private final Context mContext;
+    private final NotificationRowContentBinder mRowContentBinder;
     private final NotificationMessagingUtil mMessagingUtil;
     private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
             this::logNotificationExpansion;
+    private final NotificationRemoteInputManager mNotificationRemoteInputManager;
+    private final NotificationLockscreenUserManager mNotificationLockscreenUserManager;
     private final boolean mAllowLongPress;
     private final KeyguardBypassController mKeyguardBypassController;
     private final StatusBarStateController mStatusBarStateController;
 
-    private NotificationRemoteInputManager mRemoteInputManager;
     private NotificationPresenter mPresenter;
     private NotificationListContainer mListContainer;
     private HeadsUpManager mHeadsUpManager;
-    private NotificationContentInflater.InflationCallback mInflationCallback;
+    private NotificationRowContentBinder.InflationCallback mInflationCallback;
     private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
     private BindRowCallback mBindRowCallback;
     private NotificationClicker mNotificationClicker;
     private final NotificationLogger mNotificationLogger;
 
+    @Inject
     public NotificationRowBinderImpl(
             Context context,
-            boolean allowLongPress,
+            NotificationRemoteInputManager notificationRemoteInputManager,
+            NotificationLockscreenUserManager notificationLockscreenUserManager,
+            NotificationRowContentBinder rowContentBinder,
+            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
             KeyguardBypassController keyguardBypassController,
             StatusBarStateController statusBarStateController,
+            NotificationGroupManager notificationGroupManager,
+            NotificationGutsManager notificationGutsManager,
+            NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             NotificationLogger logger) {
         mContext = context;
+        mRowContentBinder = rowContentBinder;
         mMessagingUtil = new NotificationMessagingUtil(context);
+        mNotificationRemoteInputManager = notificationRemoteInputManager;
+        mNotificationLockscreenUserManager = notificationLockscreenUserManager;
         mAllowLongPress = allowLongPress;
         mKeyguardBypassController = keyguardBypassController;
         mStatusBarStateController = statusBarStateController;
+        mGroupManager = notificationGroupManager;
+        mGutsManager = notificationGutsManager;
+        mNotificationInterruptionStateProvider = notificationInterruptionStateProvider;
         mNotificationLogger = logger;
     }
 
-    private NotificationRemoteInputManager getRemoteInputManager() {
-        if (mRemoteInputManager == null) {
-            mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
-        }
-        return mRemoteInputManager;
-    }
-
     /**
      * Sets up late-bound dependencies for this component.
      */
     public void setUpWithPresenter(NotificationPresenter presenter,
             NotificationListContainer listContainer,
             HeadsUpManager headsUpManager,
-            NotificationContentInflater.InflationCallback inflationCallback,
             BindRowCallback bindRowCallback) {
         mPresenter = presenter;
         mListContainer = listContainer;
         mHeadsUpManager = headsUpManager;
-        mInflationCallback = inflationCallback;
         mBindRowCallback = bindRowCallback;
         mOnAppOpsClickListener = mGutsManager::openGuts;
     }
 
+    public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) {
+        mInflationCallback = callback;
+    }
+
     public void setNotificationClicker(NotificationClicker clicker) {
         mNotificationClicker = clicker;
     }
@@ -154,19 +166,6 @@
     private void bindRow(NotificationEntry entry, PackageManager pmUser,
             StatusBarNotification sbn, ExpandableNotificationRow row,
             Runnable onDismissRunnable) {
-        row.setExpansionLogger(mExpansionLogger, entry.getSbn().getKey());
-        row.setBypassController(mKeyguardBypassController);
-        row.setStatusBarStateController(mStatusBarStateController);
-        row.setGroupManager(mGroupManager);
-        row.setHeadsUpManager(mHeadsUpManager);
-        row.setOnExpandClickListener(mPresenter);
-        row.setInflationCallback(mInflationCallback);
-        if (mAllowLongPress) {
-            row.setLongPressListener(mGutsManager::openGuts);
-        }
-        mListContainer.bindRow(row);
-        getRemoteInputManager().bindRow(row);
-
         // Get the app name.
         // Note that Notification.Builder#bindHeaderAppName has similar logic
         // but since this field is used in the guts, it must be accurate.
@@ -184,15 +183,33 @@
         } catch (PackageManager.NameNotFoundException e) {
             // Do nothing
         }
-        row.setAppName(appname);
+
+        row.initialize(
+                appname,
+                sbn.getKey(),
+                mExpansionLogger,
+                mKeyguardBypassController,
+                mGroupManager,
+                mHeadsUpManager,
+                mRowContentBinder,
+                mPresenter);
+
+        // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely
+        row.setStatusBarStateController(mStatusBarStateController);
+        row.setInflationCallback(mInflationCallback);
+        row.setAppOpsOnClickListener(mOnAppOpsClickListener);
+        if (mAllowLongPress) {
+            row.setLongPressListener(mGutsManager::openGuts);
+        }
+        mListContainer.bindRow(row);
+        mNotificationRemoteInputManager.bindRow(row);
+
         row.setOnDismissRunnable(onDismissRunnable);
         row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
         if (ENABLE_REMOTE_INPUT) {
             row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
         }
 
-        row.setAppOpsOnClickListener(mOnAppOpsClickListener);
-
         mBindRowCallback.onBindRow(entry, pmUser, sbn, row);
     }
 
@@ -261,8 +278,7 @@
         if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
             row.setInflationFlags(FLAG_CONTENT_VIEW_HEADS_UP);
         }
-        row.setNeedsRedaction(
-                Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry));
+        row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry));
         row.inflateViews();
 
         // bind the click event to the content area
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 9312c22..db107f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -43,6 +43,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
@@ -62,6 +63,7 @@
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final StatusBarStateController mStatusBarStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final HighPriorityProvider mHighPriorityProvider;
 
     @Inject
     public KeyguardCoordinator(
@@ -71,15 +73,16 @@
             NotificationLockscreenUserManager lockscreenUserManager,
             BroadcastDispatcher broadcastDispatcher,
             StatusBarStateController statusBarStateController,
-            KeyguardUpdateMonitor keyguardUpdateMonitor) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            HighPriorityProvider highPriorityProvider) {
         mContext = context;
         mMainHandler = mainThreadHandler;
         mKeyguardStateController = keyguardStateController;
         mLockscreenUserManager = lockscreenUserManager;
-
         mBroadcastDispatcher = broadcastDispatcher;
         mStatusBarStateController = statusBarStateController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mHighPriorityProvider = highPriorityProvider;
     }
 
     @Override
@@ -151,7 +154,7 @@
         }
         if (NotificationUtils.useNewInterruptionModel(mContext)
                 && hideSilentNotificationsOnLockscreen()) {
-            return entry.isHighPriority();
+            return mHighPriorityProvider.isHighPriority(entry);
         } else {
             return entry.getRepresentativeEntry() != null
                     && !entry.getRepresentativeEntry().getRanking().isAmbient();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
index 13247193..2436bb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
@@ -45,14 +46,19 @@
      */
     @Inject
     public NotifCoordinators(
+            FeatureFlags featureFlags,
             KeyguardCoordinator keyguardCoordinator,
             RankingCoordinator rankingCoordinator,
             ForegroundCoordinator foregroundCoordinator,
-            DeviceProvisionedCoordinator deviceProvisionedCoordinator) {
+            DeviceProvisionedCoordinator deviceProvisionedCoordinator,
+            PreparationCoordinator preparationCoordinator) {
         mCoordinators.add(keyguardCoordinator);
         mCoordinators.add(rankingCoordinator);
         mCoordinators.add(foregroundCoordinator);
         mCoordinators.add(deviceProvisionedCoordinator);
+        if (featureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mCoordinators.add(preparationCoordinator);
+        }
         // TODO: add new Coordinators here! (b/145134683, b/112656837)
     }
 
@@ -70,6 +76,7 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println();
         pw.println(TAG + ":");
         for (Coordinator c : mCoordinators) {
             pw.println("\t" + c.getClass());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
new file mode 100644
index 0000000..a14f0e1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.NotifInflater;
+import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.logging.NotifEvent;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Kicks off notification inflation and view rebinding when a notification is added or updated.
+ * Aborts inflation when a notification is removed.
+ *
+ * If a notification is not done inflating, this coordinator will filter the notification out
+ * from the NotifListBuilder.
+ */
+@Singleton
+public class PreparationCoordinator implements Coordinator {
+    private static final String TAG = "PreparationCoordinator";
+
+    private final NotifLog mNotifLog;
+    private final NotifInflater mNotifInflater;
+    private final List<NotificationEntry> mPendingNotifications = new ArrayList<>();
+
+    @Inject
+    public PreparationCoordinator(NotifLog notifLog, NotifInflaterImpl notifInflater) {
+        mNotifLog = notifLog;
+        mNotifInflater = notifInflater;
+        mNotifInflater.setInflationCallback(mInflationCallback);
+    }
+
+    @Override
+    public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
+        notifCollection.addCollectionListener(mNotifCollectionListener);
+        notifListBuilder.addPreRenderFilter(mNotifInflationErrorFilter);
+        notifListBuilder.addPreRenderFilter(mNotifInflatingFilter);
+    }
+
+    private final NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() {
+        @Override
+        public void onEntryAdded(NotificationEntry entry) {
+            inflateEntry(entry, "entryAdded");
+        }
+
+        @Override
+        public void onEntryUpdated(NotificationEntry entry) {
+            rebind(entry, "entryUpdated");
+        }
+
+        @Override
+        public void onEntryRemoved(NotificationEntry entry, int reason, boolean removedByUser) {
+            abortInflation(entry, "entryRemoved reason=" + reason);
+        }
+    };
+
+    private final NotifFilter mNotifInflationErrorFilter = new NotifFilter(
+            TAG + "InflationError") {
+        /**
+         * Filters out notifications that threw an error when attempting to inflate.
+         */
+        @Override
+        public boolean shouldFilterOut(NotificationEntry entry, long now) {
+            if (entry.hasInflationError()) {
+                mPendingNotifications.remove(entry);
+                return true;
+            }
+            return false;
+        }
+    };
+
+    private final NotifFilter mNotifInflatingFilter = new NotifFilter(TAG + "Inflating") {
+        /**
+         * Filters out notifications that haven't been inflated yet
+         */
+        @Override
+        public boolean shouldFilterOut(NotificationEntry entry, long now) {
+            return mPendingNotifications.contains(entry);
+        }
+    };
+
+    private final NotifInflater.InflationCallback mInflationCallback =
+            new NotifInflater.InflationCallback() {
+        @Override
+        public void onInflationFinished(NotificationEntry entry) {
+            mNotifLog.log(NotifEvent.INFLATED, entry);
+            mPendingNotifications.remove(entry);
+            mNotifInflatingFilter.invalidateList();
+        }
+    };
+
+    private void inflateEntry(NotificationEntry entry, String reason) {
+        abortInflation(entry, reason);
+        mPendingNotifications.add(entry);
+        mNotifInflater.inflateViews(entry);
+    }
+
+    private void rebind(NotificationEntry entry, String reason) {
+        mNotifInflater.rebindViews(entry);
+    }
+
+    private void abortInflation(NotificationEntry entry, String reason) {
+        mNotifLog.log(NotifEvent.INFLATION_ABORTED, reason);
+        entry.abortTask();
+        mPendingNotifications.remove(entry);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java
index 5e0bd4f..8d3d0ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java
@@ -20,9 +20,12 @@
 
 import com.android.systemui.DumpController;
 import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
 import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators;
 import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer;
 
@@ -41,7 +44,9 @@
     private final NotifCollection mNotifCollection;
     private final NotifListBuilderImpl mNotifPipeline;
     private final NotifCoordinators mNotifPluggableCoordinators;
+    private final NotifInflaterImpl mNotifInflater;
     private final DumpController mDumpController;
+    private final FeatureFlags mFeatureFlags;
 
     private final FakePipelineConsumer mFakePipelineConsumer = new FakePipelineConsumer();
 
@@ -51,20 +56,30 @@
             NotifCollection notifCollection,
             NotifListBuilderImpl notifPipeline,
             NotifCoordinators notifCoordinators,
-            DumpController dumpController) {
+            NotifInflaterImpl notifInflater,
+            DumpController dumpController,
+            FeatureFlags featureFlags) {
         mGroupCoalescer = groupCoalescer;
         mNotifCollection = notifCollection;
         mNotifPipeline = notifPipeline;
         mNotifPluggableCoordinators = notifCoordinators;
         mDumpController = dumpController;
+        mNotifInflater = notifInflater;
+        mFeatureFlags = featureFlags;
     }
 
     /** Hooks the new pipeline up to NotificationManager */
     public void initialize(
-            NotificationListener notificationService) {
+            NotificationListener notificationService,
+            NotificationRowBinderImpl rowBinder) {
 
         mDumpController.registerDumpable("NotifPipeline", this);
 
+        // Setup inflation
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mNotifInflater.setRowBinder(rowBinder);
+        }
+
         // Wire up coordinators
         mFakePipelineConsumer.attach(mNotifPipeline);
         mNotifPluggableCoordinators.attach(mNotifCollection, mNotifPipeline);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java
index 069c15f..c3e3c53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java
@@ -259,7 +259,7 @@
         pw.println("Coalesced notifications:");
         for (EventBatch batch : mBatches.values()) {
             pw.println("   Batch " + batch.mGroupKey + ":");
-            pw.println("       Created" + (now - batch.mCreatedTimestamp) + "ms ago");
+            pw.println("       Created " + (now - batch.mCreatedTimestamp) + "ms ago");
             for (CoalescedEvent event : batch.mMembers) {
                 pw.println("       " + event.getKey());
                 eventCount++;
@@ -299,5 +299,5 @@
         void onNotificationBatchPosted(List<CoalescedEvent> events);
     }
 
-    private static final int GROUP_LINGER_DURATION = 40;
+    private static final int GROUP_LINGER_DURATION = 500;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java
deleted file mode 100644
index 815e6f7..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.provider;
-/**
- * Caches a computed value until invalidate() is called
- * @param <Parent> Object used to computeValue
- * @param <Value> type of value to cache until invalidate is called
- */
-public abstract class DerivedMember<Parent, Value> {
-    private Value mValue;
-    protected abstract Value computeValue(Parent parent);
-
-    /**
-     * Gets the last cached value, else recomputes the value.
-     */
-    public Value get(Parent parent) {
-        if (mValue == null) {
-            mValue = computeValue(parent);
-        }
-        return mValue;
-    }
-
-    /**
-     * Resets the cached value.
-     * Next time "get" is called, the value is recomputed.
-     */
-    public void invalidate() {
-        mValue = null;
-    }
-
-    /**
-     * Called when a NotificationEntry's status bar notification has updated.
-     * Derived members can invalidate here.
-     */
-    public void onSbnUpdated() {}
-
-    /**
-     * Called when a NotificationEntry's Ranking has updated.
-     * Derived members can invalidate here.
-     */
-    public void onRankingUpdated() {}
-
-    /**
-     * Called when a ListEntry's grouping information (parent or children) has changed.
-     * Derived members can invalidate here.
-     */
-    public void onGroupingUpdated() {}
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
new file mode 100644
index 0000000..3cc5e62
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.provider;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+
+import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Determines whether a notification is considered 'high priority'.
+ *
+ * Notifications that are high priority are visible on the lock screen/status bar and in the top
+ * section in the shade.
+ */
+@Singleton
+public class HighPriorityProvider {
+    private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+
+    @Inject
+    public HighPriorityProvider(PeopleNotificationIdentifier peopleNotificationIdentifier) {
+        mPeopleNotificationIdentifier = peopleNotificationIdentifier;
+    }
+
+    /**
+     * @return true if the ListEntry is high priority, else false
+     *
+     * A NotificationEntry is considered high priority if it:
+     *  - has importance greater than or equal to IMPORTANCE_DEFAULT
+     *  OR
+     *  - their importance has NOT been set to a low priority option by the user AND the
+     *  notification fulfills one of the following:
+     *      - has a person associated with it
+     *      - has a media session associated with it
+     *      - has messaging style
+     *
+     * A GroupEntry is considered high priority if its representativeEntry (summary) or children are
+     * high priority
+     */
+    public boolean isHighPriority(ListEntry entry) {
+        if (entry == null) {
+            return false;
+        }
+
+        final NotificationEntry notifEntry = entry.getRepresentativeEntry();
+        return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT
+                || hasHighPriorityCharacteristics(notifEntry)
+                || hasHighPriorityChild(entry);
+    }
+
+
+    private boolean hasHighPriorityChild(ListEntry entry) {
+        if (entry instanceof GroupEntry) {
+            for (NotificationEntry child : ((GroupEntry) entry).getChildren()) {
+                if (isHighPriority(child)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private boolean hasHighPriorityCharacteristics(NotificationEntry entry) {
+        return !hasUserSetImportance(entry)
+                && (isImportantOngoing(entry)
+                || entry.getSbn().getNotification().hasMediaSession()
+                || isPeopleNotification(entry)
+                || isMessagingStyle(entry));
+    }
+
+    private boolean isImportantOngoing(NotificationEntry entry) {
+        return entry.getSbn().getNotification().isForegroundService()
+                && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW;
+    }
+
+    private boolean isMessagingStyle(NotificationEntry entry) {
+        return Notification.MessagingStyle.class.equals(
+                entry.getSbn().getNotification().getNotificationStyle());
+    }
+
+    private boolean isPeopleNotification(NotificationEntry entry) {
+        return mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn());
+    }
+
+    private boolean hasUserSetImportance(NotificationEntry entry) {
+        return entry.getRanking().getChannel() != null
+                && entry.getRanking().getChannel().hasUserSetImportance();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java
deleted file mode 100644
index 76e256b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.provider;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.Person;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Whether the ListEntry is shown to the user as a high priority notification: visible on
- * the lock screen/status bar and in the top section in the shade.
- *
- * A NotificationEntry is considered high priority if it:
- *  - has importance greater than or equal to IMPORTANCE_DEFAULT
- *  OR
- *  - their importance has NOT been set to a low priority option by the user AND the notification
- *  fulfills one of the following:
- *      - has a person associated with it
- *      - has a media session associated with it
- *      - has messaging style
- *
- * A GroupEntry is considered high priority if its representativeEntry (summary) or children are
- * high priority
- */
-public class IsHighPriorityProvider extends DerivedMember<ListEntry, Boolean> {
-    // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic
-    //  replaced in GroupEntry and NotifListBuilderImpl
-    private final NotificationGroupManager mGroupManager;
-
-
-    public IsHighPriorityProvider() {
-        // TODO: (b/145659174) remove
-        mGroupManager = Dependency.get(NotificationGroupManager.class);
-    }
-
-    @Override
-    protected Boolean computeValue(ListEntry entry) {
-        if (entry == null) {
-            return false;
-        }
-
-        return isHighPriority(entry);
-    }
-
-    private boolean isHighPriority(ListEntry listEntry) {
-        // requires groups have been set (AFTER PipelineState.STATE_TRANSFORMING)
-        final NotificationEntry notifEntry = listEntry.getRepresentativeEntry();
-        return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT
-                || hasHighPriorityCharacteristics(notifEntry)
-                || hasHighPriorityChild(listEntry);
-
-    }
-
-    private boolean hasHighPriorityChild(ListEntry entry) {
-        // TODO: (b/145659174) remove
-        if (entry instanceof NotificationEntry) {
-            NotificationEntry notifEntry = (NotificationEntry) entry;
-            if (mGroupManager.isSummaryOfGroup(notifEntry.getSbn())) {
-                List<NotificationEntry> logicalChildren =
-                        mGroupManager.getLogicalChildren(notifEntry.getSbn());
-                for (NotificationEntry child : logicalChildren) {
-                    if (child.isHighPriority()) {
-                        return true;
-                    }
-                }
-            }
-        }
-
-        if (entry instanceof GroupEntry) {
-            for (NotificationEntry child : ((GroupEntry) entry).getChildren()) {
-                if (child.isHighPriority()) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private boolean hasHighPriorityCharacteristics(NotificationEntry entry) {
-        return !hasUserSetImportance(entry)
-                && (isImportantOngoing(entry)
-                || entry.getSbn().getNotification().hasMediaSession()
-                || hasPerson(entry)
-                || isMessagingStyle(entry));
-    }
-
-    private boolean isImportantOngoing(NotificationEntry entry) {
-        return entry.getSbn().getNotification().isForegroundService()
-                && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW;
-    }
-
-    private boolean isMessagingStyle(NotificationEntry entry) {
-        return Notification.MessagingStyle.class.equals(
-                entry.getSbn().getNotification().getNotificationStyle());
-    }
-
-    private boolean hasPerson(NotificationEntry entry) {
-        // TODO: cache favorite and recent contacts to check contact affinity
-        Notification notification = entry.getSbn().getNotification();
-        ArrayList<Person> people = notification.extras != null
-                ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST)
-                : new ArrayList<>();
-        return people != null && !people.isEmpty();
-    }
-
-    private boolean hasUserSetImportance(NotificationEntry entry) {
-        return entry.getRanking().getChannel() != null
-                && entry.getRanking().getChannel().hasUserSetImportance();
-    }
-
-    @Override
-    public void onSbnUpdated() {
-        invalidate();
-    }
-
-    @Override
-    public void onRankingUpdated() {
-        invalidate();
-    }
-
-    @Override
-    public void onGroupingUpdated() {
-        invalidate();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 3e1b5bd..89e5f55 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
@@ -294,6 +294,9 @@
         }
     }
 
+    /**
+     * Logs Notification inflation error
+     */
     private void logNotificationError(
             StatusBarNotification notification,
             Exception exception) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt
index 2c0c942..e81d361 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt
@@ -37,7 +37,8 @@
     val key: PersonKey,
     val name: CharSequence,
     val avatar: Drawable,
-    val clickIntent: PendingIntent
+    val clickIntent: PendingIntent,
+    val userId: Int
 )
 
 /** Unique identifier for a Person in PeopleHub. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index 784673e..88b4147 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -17,28 +17,27 @@
 package com.android.systemui.statusbar.notification.people
 
 import android.app.Notification
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.ColorFilter
-import android.graphics.PixelFormat
-import android.graphics.drawable.BitmapDrawable
+import android.content.pm.UserInfo
 import android.graphics.drawable.Drawable
-import android.os.UserHandle
+import android.os.UserManager
 import android.service.notification.StatusBarNotification
-import android.util.TypedValue
+import android.util.SparseArray
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
 import com.android.internal.statusbar.NotificationVisibility
 import com.android.internal.widget.MessagingGroup
-import com.android.launcher3.icons.BaseIconFactory
 import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.NotificationPersonExtractorPlugin
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.notification.NotificationEntryListener
 import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.policy.ExtensionController
 import java.util.ArrayDeque
+import java.util.concurrent.Executor
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -52,8 +51,7 @@
 
 @Singleton
 class NotificationPersonExtractorPluginBoundary @Inject constructor(
-    extensionController: ExtensionController,
-    private val context: Context
+    extensionController: ExtensionController
 ) : NotificationPersonExtractor {
 
     private var plugin: NotificationPersonExtractorPlugin? = null
@@ -70,9 +68,8 @@
     }
 
     override fun extractPerson(sbn: StatusBarNotification) =
-            plugin?.extractPerson(sbn)?.let { data ->
-                val badged = addBadgeToDrawable(data.avatar, context, sbn.packageName, sbn.user)
-                PersonModel(data.key, data.name, badged, data.clickIntent)
+            plugin?.extractPerson(sbn)?.run {
+                PersonModel(key, name, avatar, clickIntent, sbn.user.identifier)
             }
 
     override fun extractPersonKey(sbn: StatusBarNotification) = plugin?.extractPersonKey(sbn)
@@ -84,11 +81,16 @@
 @Singleton
 class PeopleHubDataSourceImpl @Inject constructor(
     private val notificationEntryManager: NotificationEntryManager,
-    private val peopleHubManager: PeopleHubManager,
-    private val extractor: NotificationPersonExtractor
+    private val extractor: NotificationPersonExtractor,
+    private val userManager: UserManager,
+    @Background private val bgExecutor: Executor,
+    @Main private val mainExecutor: Executor,
+    private val notifLockscreenUserMgr: NotificationLockscreenUserManager
 ) : DataSource<PeopleHubModel> {
 
+    private var userChangeSubscription: Subscription? = null
     private val dataListeners = mutableListOf<DataListener<PeopleHubModel>>()
+    private val peopleHubManagerForUser = SparseArray<PeopleHubManager>()
 
     private val notificationEntryListener = object : NotificationEntryListener {
         override fun onEntryInflated(entry: NotificationEntry, inflatedFlags: Int) =
@@ -106,31 +108,56 @@
     }
 
     private fun removeVisibleEntry(entry: NotificationEntry) {
-        val key = extractor.extractPersonKey(entry.sbn) ?: entry.extractPersonKey()
-        if (key?.let(peopleHubManager::removeActivePerson) == true) {
-            updateUi()
+        (extractor.extractPersonKey(entry.sbn) ?: entry.extractPersonKey())?.let { key ->
+            val userId = entry.sbn.user.identifier
+            bgExecutor.execute {
+                val parentId = userManager.getProfileParent(userId)?.id ?: userId
+                mainExecutor.execute {
+                    if (peopleHubManagerForUser[parentId]?.removeActivePerson(key) == true) {
+                        updateUi()
+                    }
+                }
+            }
         }
     }
 
     private fun addVisibleEntry(entry: NotificationEntry) {
-        val personModel = extractor.extractPerson(entry.sbn) ?: entry.extractPerson()
-        if (personModel?.let(peopleHubManager::addActivePerson) == true) {
-            updateUi()
+        (extractor.extractPerson(entry.sbn) ?: entry.extractPerson())?.let { personModel ->
+            val userId = entry.sbn.user.identifier
+            bgExecutor.execute {
+                val parentId = userManager.getProfileParent(userId)?.id ?: userId
+                mainExecutor.execute {
+                    val manager = peopleHubManagerForUser[parentId]
+                            ?: PeopleHubManager().also { peopleHubManagerForUser.put(parentId, it) }
+                    if (manager.addActivePerson(personModel)) {
+                        updateUi()
+                    }
+                }
+            }
         }
     }
 
     override fun registerListener(listener: DataListener<PeopleHubModel>): Subscription {
-        val registerWithNotificationEntryManager = dataListeners.isEmpty()
+        val register = dataListeners.isEmpty()
         dataListeners.add(listener)
-        if (registerWithNotificationEntryManager) {
+        if (register) {
+            userChangeSubscription = notifLockscreenUserMgr.registerListener(
+                    object : NotificationLockscreenUserManager.UserChangedListener {
+                        override fun onUserChanged(userId: Int) = updateUi()
+                        override fun onCurrentProfilesChanged(
+                            currentProfiles: SparseArray<UserInfo>?
+                        ) = updateUi()
+                    })
             notificationEntryManager.addNotificationEntryListener(notificationEntryListener)
         } else {
-            listener.onDataChanged(peopleHubManager.getPeopleHubModel())
+            getPeopleHubModelForCurrentUser()?.let(listener::onDataChanged)
         }
         return object : Subscription {
             override fun unsubscribe() {
                 dataListeners.remove(listener)
                 if (dataListeners.isEmpty()) {
+                    userChangeSubscription?.unsubscribe()
+                    userChangeSubscription = null
                     notificationEntryManager
                             .removeNotificationEntryListener(notificationEntryListener)
                 }
@@ -138,16 +165,36 @@
         }
     }
 
+    private fun getPeopleHubModelForCurrentUser(): PeopleHubModel? {
+        val currentUserId = notifLockscreenUserMgr.currentUserId
+        val model = peopleHubManagerForUser[currentUserId]?.getPeopleHubModel()
+                ?: return null
+        val currentProfiles = notifLockscreenUserMgr.currentProfiles
+        return model.copy(people = model.people.filter { person ->
+            currentProfiles[person.userId]?.isQuietModeEnabled == false
+        })
+    }
+
     private fun updateUi() {
-        val model = peopleHubManager.getPeopleHubModel()
+        val model = getPeopleHubModelForCurrentUser() ?: return
         for (listener in dataListeners) {
             listener.onDataChanged(model)
         }
     }
 }
 
-@Singleton
-class PeopleHubManager @Inject constructor() {
+private fun NotificationLockscreenUserManager.registerListener(
+    listener: NotificationLockscreenUserManager.UserChangedListener
+): Subscription {
+    addUserChangedListener(listener)
+    return object : Subscription {
+        override fun unsubscribe() {
+            removeUserChangedListener(listener)
+        }
+    }
+}
+
+class PeopleHubManager {
 
     private val activePeople = mutableMapOf<PersonKey, PersonModel>()
     private val inactivePeople = ArrayDeque<PersonModel>(MAX_STORED_INACTIVE_PEOPLE)
@@ -157,7 +204,7 @@
             if (inactivePeople.size >= MAX_STORED_INACTIVE_PEOPLE) {
                 inactivePeople.removeLast()
             }
-            inactivePeople.push(data)
+            inactivePeople.add(data)
             return true
         }
         return false
@@ -190,63 +237,7 @@
             ?: extras.getString(Notification.EXTRA_TITLE)
             ?: return null
     val drawable = extractAvatarFromRow(this) ?: return null
-    val badgedAvatar = addBadgeToDrawable(drawable, row.context, sbn.packageName, sbn.user)
-    return PersonModel(key, name, badgedAvatar, clickIntent)
-}
-
-private fun addBadgeToDrawable(
-    drawable: Drawable,
-    context: Context,
-    packageName: String,
-    user: UserHandle
-): Drawable {
-    val pm = context.packageManager
-    val appInfo = pm.getApplicationInfoAsUser(packageName, 0, user)
-    return object : Drawable() {
-        override fun draw(canvas: Canvas) {
-            val iconBounds = getBounds()
-            val factory = object : BaseIconFactory(
-                    context,
-                    0 /* unused */,
-                    iconBounds.width(),
-                    true) {}
-            val badge = factory.createBadgedIconBitmap(
-                    appInfo.loadIcon(pm),
-                    user,
-                    true,
-                    appInfo.isInstantApp,
-                    null)
-            val badgeDrawable = BitmapDrawable(context.resources, badge.icon)
-                    .apply {
-                        alpha = drawable.alpha
-                        colorFilter = drawable.colorFilter
-                        val badgeWidth = TypedValue.applyDimension(
-                                TypedValue.COMPLEX_UNIT_DIP,
-                                15f,
-                                context.resources.displayMetrics
-                        ).toInt()
-                        setBounds(
-                                iconBounds.left + (iconBounds.width() - badgeWidth),
-                                iconBounds.top + (iconBounds.height() - badgeWidth),
-                                iconBounds.right,
-                                iconBounds.bottom)
-                    }
-            drawable.bounds = iconBounds
-            drawable.draw(canvas)
-            badgeDrawable.draw(canvas)
-        }
-
-        override fun setAlpha(alpha: Int) {
-            drawable.alpha = alpha
-        }
-
-        override fun setColorFilter(colorFilter: ColorFilter?) {
-            drawable.colorFilter = colorFilter
-        }
-
-        @PixelFormat.Opacity
-        override fun getOpacity(): Int = PixelFormat.OPAQUE
-    }
+    return PersonModel(key, name, drawable, clickIntent, sbn.user.identifier)
 }
 
 fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
@@ -272,4 +263,4 @@
         if (isMessagingNotification()) key else null
 
 private fun NotificationEntry.isMessagingNotification() =
-        sbn.notification.notificationStyle == Notification.MessagingStyle::class.java
\ No newline at end of file
+        sbn.notification.notificationStyle == Notification.MessagingStyle::class.java
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 3c247df..a8a35d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -65,7 +65,6 @@
 import android.widget.Chronometer;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
-import android.widget.RemoteViews;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
@@ -150,7 +149,7 @@
     private StatusBarStateController mStatusbarStateController;
     private KeyguardBypassController mBypassController;
     private LayoutListener mLayoutListener;
-    private final NotificationContentInflater mNotificationInflater;
+    private NotificationRowContentBinder mNotificationContentBinder;
     private int mIconTransformContentShift;
     private int mIconTransformContentShiftNoIcon;
     private int mMaxHeadsUpHeightBeforeN;
@@ -464,7 +463,7 @@
      * Inflate views based off the inflation flags set. Inflation happens asynchronously.
      */
     public void inflateViews() {
-        mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams,
+        mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams,
                 false /* forceInflate */, mInflationCallback);
     }
 
@@ -478,7 +477,7 @@
         // View should not be reinflated in the future
         clearInflationFlags(inflationFlag);
         Runnable freeViewRunnable =
-                () -> mNotificationInflater.unbindContent(mEntry, this, inflationFlag);
+                () -> mNotificationContentBinder.unbindContent(mEntry, this, inflationFlag);
         switch (inflationFlag) {
             case FLAG_CONTENT_VIEW_HEADS_UP:
                 getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP,
@@ -742,23 +741,10 @@
         return mIsHeadsUp || mHeadsupDisappearRunning;
     }
 
-
-    public void setGroupManager(NotificationGroupManager groupManager) {
-        mGroupManager = groupManager;
-        mPrivateLayout.setGroupManager(groupManager);
-    }
-
     public void setRemoteInputController(RemoteInputController r) {
         mPrivateLayout.setRemoteInputController(r);
     }
 
-    public void setAppName(String appName) {
-        mAppName = appName;
-        if (mMenuRow != null && mMenuRow.getMenuView() != null) {
-            mMenuRow.setAppName(mAppName);
-        }
-    }
-
     public void addChildNotification(ExpandableNotificationRow row) {
         addChildNotification(row, -1);
     }
@@ -852,7 +838,7 @@
             mIsChildInGroup = isChildInGroup;
             if (mIsLowPriority) {
                 int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
-                mNotificationInflater.bindContent(mEntry, this, flags, mBindParams,
+                mNotificationContentBinder.bindContent(mEntry, this, flags, mBindParams,
                         false /* forceInflate */, mInflationCallback);
             }
         }
@@ -1105,10 +1091,6 @@
         return mPrivateLayout.getContractedNotificationHeader();
     }
 
-    public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) {
-        mOnExpandClickListener = onExpandClickListener;
-    }
-
     public void setLongPressListener(LongPressListener longPressListener) {
         mLongPressListener = longPressListener;
     }
@@ -1131,10 +1113,6 @@
         }
     }
 
-    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
-        mHeadsUpManager = headsUpManager;
-    }
-
     public HeadsUpManager getHeadsUpManager() {
         return mHeadsUpManager;
     }
@@ -1259,7 +1237,7 @@
             l.reInflateViews();
         }
         mEntry.getSbn().clearPackageContext();
-        mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams,
+        mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams,
                 true /* forceInflate */, mInflationCallback);
     }
 
@@ -1634,10 +1612,6 @@
         mBindParams.usesIncreasedHeadsUpHeight = use;
     }
 
-    public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
-        mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler);
-    }
-
     /**
      * Set callback for notification content inflation
      *
@@ -1652,7 +1626,7 @@
             mNeedsRedaction = needsRedaction;
             if (needsRedaction) {
                 setInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
-                mNotificationInflater.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC,
+                mNotificationContentBinder.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC,
                         mBindParams, false /* forceInflate */, mInflationCallback);
             } else {
                 clearInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
@@ -1661,18 +1635,12 @@
         }
     }
 
-    @VisibleForTesting
-    public NotificationContentInflater getNotificationInflater() {
-        return mNotificationInflater;
-    }
-
     public interface ExpansionLogger {
         void logNotificationExpansion(String key, boolean userAction, boolean expanded);
     }
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mNotificationInflater = new NotificationContentInflater();
         mMenuRow = new NotificationMenuRow(mContext);
         mImageResolver = new NotificationInlineImageResolver(context,
                 new NotificationInlineImageCache());
@@ -1680,8 +1648,30 @@
         initDimens();
     }
 
-    public void setBypassController(KeyguardBypassController bypassController) {
+    /**
+     * Initialize row.
+     */
+    public void initialize(
+            String appName,
+            String notificationKey,
+            ExpansionLogger logger,
+            KeyguardBypassController bypassController,
+            NotificationGroupManager groupManager,
+            HeadsUpManager headsUpManager,
+            NotificationRowContentBinder rowContentBinder,
+            OnExpandClickListener onExpandClickListener) {
+        mAppName = appName;
+        if (mMenuRow != null && mMenuRow.getMenuView() != null) {
+            mMenuRow.setAppName(mAppName);
+        }
+        mLogger = logger;
+        mLoggingKey = notificationKey;
         mBypassController = bypassController;
+        mGroupManager = groupManager;
+        mPrivateLayout.setGroupManager(groupManager);
+        mHeadsUpManager = headsUpManager;
+        mNotificationContentBinder = rowContentBinder;
+        mOnExpandClickListener = onExpandClickListener;
     }
 
     public void setStatusBarStateController(StatusBarStateController statusBarStateController) {
@@ -2920,11 +2910,6 @@
         return 0;
     }
 
-    public void setExpansionLogger(ExpansionLogger logger, String key) {
-        mLogger = logger;
-        mLoggingKey = key;
-    }
-
     public void onExpandedByGesture(boolean userExpanded) {
         int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER;
         if (mGroupManager.isSummaryOfGroup(mEntry.getSbn())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java
new file mode 100644
index 0000000..c11c60f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import android.widget.RemoteViews;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+
+/**
+ * Caches {@link RemoteViews} for a notification's content views.
+ */
+public interface NotifRemoteViewCache {
+
+    /**
+     * Whether the notification has the remote view cached
+     *
+     * @param entry notification
+     * @param flag inflation flag for content view
+     * @return true if the remote view is cached
+     */
+    boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag);
+
+    /**
+     * Get the remote view for the content flag specified.
+     *
+     * @param entry notification
+     * @param flag inflation flag for the content view
+     * @return the remote view if it is cached, null otherwise
+     */
+    @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag);
+
+    /**
+     * Cache a remote view for a given content flag on a notification.
+     *
+     * @param entry notification
+     * @param flag inflation flag for the content view
+     * @param remoteView remote view to store
+     */
+    void putCachedView(
+            NotificationEntry entry,
+            @InflationFlag int flag,
+            RemoteViews remoteView);
+
+    /**
+     * Remove a cached remote view for a given content flag on a notification.
+     *
+     * @param entry notification
+     * @param flag inflation flag for the content view
+     */
+    void removeCachedView(NotificationEntry entry, @InflationFlag int flag);
+
+    /**
+     * Clear a notification's remote view cache.
+     *
+     * @param entry notification
+     */
+    void clearCache(NotificationEntry entry);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
new file mode 100644
index 0000000..a6e5c2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.widget.RemoteViews;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+/**
+ * Implementation of remote view cache that keeps remote views cached for all active notifications.
+ */
+public class NotifRemoteViewCacheImpl implements NotifRemoteViewCache {
+    private final Map<NotificationEntry, SparseArray<RemoteViews>> mNotifCachedContentViews =
+            new ArrayMap<>();
+
+    @Inject
+    NotifRemoteViewCacheImpl(NotificationEntryManager entryManager) {
+        entryManager.addNotificationEntryListener(mEntryListener);
+    }
+
+    @Override
+    public boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag) {
+        return getCachedView(entry, flag) != null;
+    }
+
+    @Override
+    public @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag) {
+        SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+        if (contentViews == null) {
+            return null;
+        }
+        return contentViews.get(flag);
+    }
+
+    @Override
+    public void putCachedView(
+            NotificationEntry entry,
+            @InflationFlag int flag,
+            RemoteViews remoteView) {
+        /**
+         * TODO: We should be more strict here in the future (i.e. throw an exception) if the
+         * content views aren't created. We don't do that right now because we have edge cases
+         * where we may bind/unbind content after a notification is removed.
+         */
+        SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+        if (contentViews == null) {
+            return;
+        }
+        contentViews.put(flag, remoteView);
+    }
+
+    @Override
+    public void removeCachedView(NotificationEntry entry, @InflationFlag int flag) {
+        SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+        if (contentViews == null) {
+            return;
+        }
+        contentViews.remove(flag);
+    }
+
+    @Override
+    public void clearCache(NotificationEntry entry) {
+        SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+        if (contentViews == null) {
+            return;
+        }
+        contentViews.clear();
+    }
+
+    private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
+        @Override
+        public void onPendingEntryAdded(NotificationEntry entry) {
+            mNotifCachedContentViews.put(entry, new SparseArray<>());
+        }
+
+        @Override
+        public void onEntryRemoved(
+                NotificationEntry entry,
+                @Nullable NotificationVisibility visibility,
+                boolean removedByUser) {
+            mNotifCachedContentViews.remove(entry);
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 172b72e..e1a6747 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
 
@@ -26,7 +27,6 @@
 import android.os.AsyncTask;
 import android.os.CancellationSignal;
 import android.service.notification.StatusBarNotification;
-import android.util.ArrayMap;
 import android.util.Log;
 import android.view.View;
 import android.widget.RemoteViews;
@@ -35,6 +35,7 @@
 import com.android.internal.widget.ImageMessageConsumer;
 import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.InflationTask;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
@@ -49,17 +50,30 @@
 
 import java.util.HashMap;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
 /**
  * {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by
  * asynchronously building the content's {@link RemoteViews} and applying it to the row.
  */
+@Singleton
+@VisibleForTesting(visibility = PACKAGE)
 public class NotificationContentInflater implements NotificationRowContentBinder {
 
     public static final String TAG = "NotifContentInflater";
 
-    private RemoteViews.OnClickHandler mRemoteViewClickHandler;
     private boolean mInflateSynchronously = false;
-    private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>();
+    private final NotificationRemoteInputManager mRemoteInputManager;
+    private final NotifRemoteViewCache mRemoteViewCache;
+
+    @Inject
+    public NotificationContentInflater(
+            NotifRemoteViewCache remoteViewCache,
+            NotificationRemoteInputManager remoteInputManager) {
+        mRemoteViewCache = remoteViewCache;
+        mRemoteInputManager = remoteInputManager;
+    }
 
     @Override
     public void bindContent(
@@ -76,27 +90,27 @@
             return;
         }
 
-        StatusBarNotification sbn = row.getEntry().getSbn();
+        StatusBarNotification sbn = entry.getSbn();
 
         // To check if the notification has inline image and preload inline image if necessary.
         row.getImageResolver().preloadImages(sbn.getNotification());
 
         if (forceInflate) {
-            mCachedContentViews.clear();
+            mRemoteViewCache.clearCache(entry);
         }
 
         AsyncInflationTask task = new AsyncInflationTask(
-                sbn,
                 mInflateSynchronously,
                 contentToBind,
-                mCachedContentViews,
+                mRemoteViewCache,
+                entry,
                 row,
                 bindParams.isLowPriority,
                 bindParams.isChildInGroup,
                 bindParams.usesIncreasedHeight,
                 bindParams.usesIncreasedHeadsUpHeight,
                 callback,
-                mRemoteViewClickHandler);
+                mRemoteInputManager.getRemoteViewsOnClickHandler());
         if (mInflateSynchronously) {
             task.onPostExecute(task.doInBackground());
         } else {
@@ -123,13 +137,15 @@
         result = inflateSmartReplyViews(result, reInflateFlags, entry,
                 row.getContext(), packageContext, row.getHeadsUpManager(),
                 row.getExistingSmartRepliesAndActions());
+
         apply(
                 inflateSynchronously,
                 result,
                 reInflateFlags,
-                mCachedContentViews,
+                mRemoteViewCache,
+                entry,
                 row,
-                mRemoteViewClickHandler,
+                mRemoteInputManager.getRemoteViewsOnClickHandler(),
                 null);
         return result;
     }
@@ -149,7 +165,7 @@
         int curFlag = 1;
         while (contentToUnbind != 0) {
             if ((contentToUnbind & curFlag) != 0) {
-                freeNotificationView(row, curFlag);
+                freeNotificationView(entry, row, curFlag);
             }
             contentToUnbind &= ~curFlag;
             curFlag = curFlag << 1;
@@ -157,34 +173,25 @@
     }
 
     /**
-     * Set click handler for notification remote views
-     *
-     * @param remoteViewClickHandler click handler for remote views
-     */
-    public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
-        mRemoteViewClickHandler = remoteViewClickHandler;
-    }
-
-    /**
      * Frees the content view associated with the inflation flag.  Will only succeed if the
      * view is safe to remove.
      *
      * @param inflateFlag the flag corresponding to the content view which should be freed
      */
-    private void freeNotificationView(ExpandableNotificationRow row,
+    private void freeNotificationView(NotificationEntry entry, ExpandableNotificationRow row,
             @InflationFlag int inflateFlag) {
         switch (inflateFlag) {
             case FLAG_CONTENT_VIEW_HEADS_UP:
                 if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) {
                     row.getPrivateLayout().setHeadsUpChild(null);
-                    mCachedContentViews.remove(FLAG_CONTENT_VIEW_HEADS_UP);
+                    mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
                     row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null);
                 }
                 break;
             case FLAG_CONTENT_VIEW_PUBLIC:
                 if (row.getPublicLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) {
                     row.getPublicLayout().setContractedChild(null);
-                    mCachedContentViews.remove(FLAG_CONTENT_VIEW_PUBLIC);
+                    mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC);
                 }
                 break;
             case FLAG_CONTENT_VIEW_CONTRACTED:
@@ -245,11 +252,12 @@
         return result;
     }
 
-    public static CancellationSignal apply(
+    private static CancellationSignal apply(
             boolean inflateSynchronously,
             InflationProgress result,
             @InflationFlag int reInflateFlags,
-            ArrayMap<Integer, RemoteViews> cachedContentViews,
+            NotifRemoteViewCache remoteViewCache,
+            NotificationEntry entry,
             ExpandableNotificationRow row,
             RemoteViews.OnClickHandler remoteViewClickHandler,
             @Nullable InflationCallback callback) {
@@ -261,7 +269,7 @@
         if ((reInflateFlags & flag) != 0) {
             boolean isNewView =
                     !canReapplyRemoteView(result.newContentView,
-                            cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED));
+                            remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED));
             ApplyCallback applyCallback = new ApplyCallback() {
                 @Override
                 public void setResultView(View v) {
@@ -273,8 +281,8 @@
                     return result.newContentView;
                 }
             };
-            applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews,
-                    row, isNewView, remoteViewClickHandler, callback, privateLayout,
+            applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+                    entry, row, isNewView, remoteViewClickHandler, callback, privateLayout,
                     privateLayout.getContractedChild(), privateLayout.getVisibleWrapper(
                             NotificationContentView.VISIBLE_TYPE_CONTRACTED),
                     runningInflations, applyCallback);
@@ -285,7 +293,7 @@
             if (result.newExpandedView != null) {
                 boolean isNewView =
                         !canReapplyRemoteView(result.newExpandedView,
-                                cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED));
+                                remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED));
                 ApplyCallback applyCallback = new ApplyCallback() {
                     @Override
                     public void setResultView(View v) {
@@ -297,8 +305,8 @@
                         return result.newExpandedView;
                     }
                 };
-                applyRemoteView(inflateSynchronously, result, reInflateFlags, flag,
-                        cachedContentViews, row, isNewView, remoteViewClickHandler,
+                applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+                        entry, row, isNewView, remoteViewClickHandler,
                         callback, privateLayout, privateLayout.getExpandedChild(),
                         privateLayout.getVisibleWrapper(
                                 NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations,
@@ -311,7 +319,7 @@
             if (result.newHeadsUpView != null) {
                 boolean isNewView =
                         !canReapplyRemoteView(result.newHeadsUpView,
-                                cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP));
+                                remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP));
                 ApplyCallback applyCallback = new ApplyCallback() {
                     @Override
                     public void setResultView(View v) {
@@ -323,8 +331,8 @@
                         return result.newHeadsUpView;
                     }
                 };
-                applyRemoteView(inflateSynchronously, result, reInflateFlags, flag,
-                        cachedContentViews, row, isNewView, remoteViewClickHandler,
+                applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+                        entry, row, isNewView, remoteViewClickHandler,
                         callback, privateLayout, privateLayout.getHeadsUpChild(),
                         privateLayout.getVisibleWrapper(
                                 VISIBLE_TYPE_HEADSUP), runningInflations,
@@ -336,7 +344,7 @@
         if ((reInflateFlags & flag) != 0) {
             boolean isNewView =
                     !canReapplyRemoteView(result.newPublicView,
-                            cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC));
+                            remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC));
             ApplyCallback applyCallback = new ApplyCallback() {
                 @Override
                 public void setResultView(View v) {
@@ -348,15 +356,16 @@
                     return result.newPublicView;
                 }
             };
-            applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews,
-                    row, isNewView, remoteViewClickHandler, callback,
+            applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+                    entry, row, isNewView, remoteViewClickHandler, callback,
                     publicLayout, publicLayout.getContractedChild(),
                     publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED),
                     runningInflations, applyCallback);
         }
 
         // Let's try to finish, maybe nobody is even inflating anything
-        finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations, callback, row);
+        finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, callback, entry,
+                row);
         CancellationSignal cancellationSignal = new CancellationSignal();
         cancellationSignal.setOnCancelListener(
                 () -> runningInflations.values().forEach(CancellationSignal::cancel));
@@ -369,7 +378,8 @@
             final InflationProgress result,
             final @InflationFlag int reInflateFlags,
             @InflationFlag int inflationId,
-            final ArrayMap<Integer, RemoteViews> cachedContentViews,
+            final NotifRemoteViewCache remoteViewCache,
+            final NotificationEntry entry,
             final ExpandableNotificationRow row,
             boolean isNewView,
             RemoteViews.OnClickHandler remoteViewClickHandler,
@@ -397,7 +407,7 @@
                     existingWrapper.onReinflated();
                 }
             } catch (Exception e) {
-                handleInflationError(runningInflations, e, row.getEntry().getSbn(), callback);
+                handleInflationError(runningInflations, e, row.getEntry(), callback);
                 // Add a running inflation to make sure we don't trigger callbacks.
                 // Safe to do because only happens in tests.
                 runningInflations.put(inflationId, new CancellationSignal());
@@ -422,8 +432,8 @@
                     existingWrapper.onReinflated();
                 }
                 runningInflations.remove(inflationId);
-                finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations,
-                        callback, row);
+                finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations,
+                        callback, entry, row);
             }
 
             @Override
@@ -448,7 +458,7 @@
                     onViewApplied(newView);
                 } catch (Exception anotherException) {
                     runningInflations.remove(inflationId);
-                    handleInflationError(runningInflations, e, row.getEntry().getSbn(),
+                    handleInflationError(runningInflations, e, row.getEntry(),
                             callback);
                 }
             }
@@ -474,7 +484,7 @@
 
     private static void handleInflationError(
             HashMap<Integer, CancellationSignal> runningInflations, Exception e,
-            StatusBarNotification notification, @Nullable InflationCallback callback) {
+            NotificationEntry notification, @Nullable InflationCallback callback) {
         Assert.isMainThread();
         runningInflations.values().forEach(CancellationSignal::cancel);
         if (callback != null) {
@@ -488,11 +498,11 @@
      * @return true if the inflation was finished
      */
     private static boolean finishIfDone(InflationProgress result,
-            @InflationFlag int reInflateFlags, ArrayMap<Integer, RemoteViews> cachedContentViews,
+            @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache,
             HashMap<Integer, CancellationSignal> runningInflations,
-            @Nullable InflationCallback endListener, ExpandableNotificationRow row) {
+            @Nullable InflationCallback endListener, NotificationEntry entry,
+            ExpandableNotificationRow row) {
         Assert.isMainThread();
-        NotificationEntry entry = row.getEntry();
         NotificationContentView privateLayout = row.getPrivateLayout();
         NotificationContentView publicLayout = row.getPublicLayout();
         if (runningInflations.isEmpty()) {
@@ -500,23 +510,27 @@
                 if (result.inflatedContentView != null) {
                     // New view case
                     privateLayout.setContractedChild(result.inflatedContentView);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView);
-                } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED) != null) {
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+                            result.newContentView);
+                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) {
                     // Reinflation case. Only update if it's still cached (i.e. view has not been
                     // freed while inflating).
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView);
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+                            result.newContentView);
                 }
             }
 
             if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
                 if (result.inflatedExpandedView != null) {
                     privateLayout.setExpandedChild(result.inflatedExpandedView);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView);
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+                            result.newExpandedView);
                 } else if (result.newExpandedView == null) {
                     privateLayout.setExpandedChild(null);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, null);
-                } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED) != null) {
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView);
+                    remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
+                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) {
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+                            result.newExpandedView);
                 }
                 if (result.newExpandedView != null) {
                     privateLayout.setExpandedInflatedSmartReplies(
@@ -530,12 +544,14 @@
             if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
                 if (result.inflatedHeadsUpView != null) {
                     privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView);
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+                            result.newHeadsUpView);
                 } else if (result.newHeadsUpView == null) {
                     privateLayout.setHeadsUpChild(null);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, null);
-                } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP) != null) {
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView);
+                    remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
+                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) {
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+                            result.newHeadsUpView);
                 }
                 if (result.newHeadsUpView != null) {
                     privateLayout.setHeadsUpInflatedSmartReplies(
@@ -548,16 +564,18 @@
             if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
                 if (result.inflatedPublicView != null) {
                     publicLayout.setContractedChild(result.inflatedPublicView);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView);
-                } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC) != null) {
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView);
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+                            result.newPublicView);
+                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) {
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+                            result.newPublicView);
                 }
             }
 
             entry.headsUpStatusBarText = result.headsUpStatusBarText;
             entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
             if (endListener != null) {
-                endListener.onAsyncInflationFinished(row.getEntry(), reInflateFlags);
+                endListener.onAsyncInflationFinished(entry, reInflateFlags);
             }
             return true;
         }
@@ -615,7 +633,7 @@
     public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
             implements InflationCallback, InflationTask {
 
-        private final StatusBarNotification mSbn;
+        private final NotificationEntry mEntry;
         private final Context mContext;
         private final boolean mInflateSynchronously;
         private final boolean mIsLowPriority;
@@ -624,17 +642,17 @@
         private final InflationCallback mCallback;
         private final boolean mUsesIncreasedHeadsUpHeight;
         private @InflationFlag int mReInflateFlags;
-        private final ArrayMap<Integer, RemoteViews> mCachedContentViews;
+        private final NotifRemoteViewCache mRemoteViewCache;
         private ExpandableNotificationRow mRow;
         private Exception mError;
         private RemoteViews.OnClickHandler mRemoteViewClickHandler;
         private CancellationSignal mCancellationSignal;
 
         private AsyncInflationTask(
-                StatusBarNotification notification,
                 boolean inflateSynchronously,
                 @InflationFlag int reInflateFlags,
-                ArrayMap<Integer, RemoteViews> cachedContentViews,
+                NotifRemoteViewCache cache,
+                NotificationEntry entry,
                 ExpandableNotificationRow row,
                 boolean isLowPriority,
                 boolean isChildInGroup,
@@ -642,11 +660,11 @@
                 boolean usesIncreasedHeadsUpHeight,
                 InflationCallback callback,
                 RemoteViews.OnClickHandler remoteViewClickHandler) {
+            mEntry = entry;
             mRow = row;
-            mSbn = notification;
             mInflateSynchronously = inflateSynchronously;
             mReInflateFlags = reInflateFlags;
-            mCachedContentViews = cachedContentViews;
+            mRemoteViewCache = cache;
             mContext = mRow.getContext();
             mIsLowPriority = isLowPriority;
             mIsChildInGroup = isChildInGroup;
@@ -654,7 +672,6 @@
             mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight;
             mRemoteViewClickHandler = remoteViewClickHandler;
             mCallback = callback;
-            NotificationEntry entry = row.getEntry();
             entry.setInflationTask(this);
         }
 
@@ -667,12 +684,13 @@
         @Override
         protected InflationProgress doInBackground(Void... params) {
             try {
+                final StatusBarNotification sbn = mEntry.getSbn();
                 final Notification.Builder recoveredBuilder
                         = Notification.Builder.recoverBuilder(mContext,
-                        mSbn.getNotification());
+                        sbn.getNotification());
 
-                Context packageContext = mSbn.getPackageContext(mContext);
-                Notification notification = mSbn.getNotification();
+                Context packageContext = sbn.getPackageContext(mContext);
+                Notification notification = sbn.getNotification();
                 if (notification.isMediaNotification()) {
                     MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
                             packageContext);
@@ -681,7 +699,7 @@
                 InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
                         recoveredBuilder, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight,
                         mUsesIncreasedHeadsUpHeight, packageContext);
-                return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(),
+                return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry,
                         mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
                         mRow.getExistingSmartRepliesAndActions());
             } catch (Exception e) {
@@ -694,20 +712,20 @@
         protected void onPostExecute(InflationProgress result) {
             if (mError == null) {
                 mCancellationSignal = apply(mInflateSynchronously, result, mReInflateFlags,
-                        mCachedContentViews, mRow, mRemoteViewClickHandler, this);
+                        mRemoteViewCache, mEntry, mRow, mRemoteViewClickHandler, this);
             } else {
                 handleError(mError);
             }
         }
 
         private void handleError(Exception e) {
-            mRow.getEntry().onInflationTaskFinished();
-            StatusBarNotification sbn = mRow.getEntry().getSbn();
+            mEntry.onInflationTaskFinished();
+            StatusBarNotification sbn = mEntry.getSbn();
             final String ident = sbn.getPackageName() + "/0x"
                     + Integer.toHexString(sbn.getId());
             Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
             if (mCallback != null) {
-                mCallback.handleInflationException(sbn,
+                mCallback.handleInflationException(mRow.getEntry(),
                         new InflationException("Couldn't inflate contentViews" + e));
             }
         }
@@ -729,17 +747,17 @@
         }
 
         @Override
-        public void handleInflationException(StatusBarNotification notification, Exception e) {
+        public void handleInflationException(NotificationEntry entry, Exception e) {
             handleError(e);
         }
 
         @Override
         public void onAsyncInflationFinished(NotificationEntry entry,
                 @InflationFlag int inflatedFlags) {
-            mRow.getEntry().onInflationTaskFinished();
+            mEntry.onInflationTaskFinished();
             mRow.onNotificationUpdated();
             if (mCallback != null) {
-                mCallback.onAsyncInflationFinished(mRow.getEntry(), inflatedFlags);
+                mCallback.onAsyncInflationFinished(mEntry, inflatedFlags);
             }
 
             // Notify the resolver that the inflation task has finished,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 6f2abba..779a224 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -53,6 +53,7 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -81,6 +82,7 @@
     private final Context mContext;
     private final VisualStabilityManager mVisualStabilityManager;
     private final AccessibilityManager mAccessibilityManager;
+    private final HighPriorityProvider mHighPriorityProvider;
 
     // Dependencies:
     private final NotificationLockscreenUserManager mLockscreenUserManager =
@@ -109,12 +111,14 @@
     @Inject
     public NotificationGutsManager(Context context, VisualStabilityManager visualStabilityManager,
             Lazy<StatusBar> statusBarLazy, @Main Handler mainHandler,
-            AccessibilityManager accessibilityManager) {
+            AccessibilityManager accessibilityManager,
+            HighPriorityProvider highPriorityProvider) {
         mContext = context;
         mVisualStabilityManager = visualStabilityManager;
         mStatusBarLazy = statusBarLazy;
         mMainHandler = mainHandler;
         mAccessibilityManager = accessibilityManager;
+        mHighPriorityProvider = highPriorityProvider;
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
@@ -331,8 +335,7 @@
                 row.getIsNonblockable(),
                 isForBlockingHelper,
                 row.getEntry().getImportance(),
-                row.getEntry().isHighPriority());
-
+                mHighPriorityProvider.isHighPriority(row.getEntry()));
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index b4ccb56..edfd1b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -46,6 +46,7 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
 import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent;
+import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 
 import java.util.ArrayList;
@@ -269,7 +270,8 @@
         }
         mAppOpsItem = createAppOpsItem(mContext);
         if (mIsUsingBidirectionalSwipe) {
-            mInfoItem = createInfoItem(mContext, !mParent.getEntry().isHighPriority());
+            mInfoItem = createInfoItem(mContext,
+                    mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_SILENT);
         } else {
             mInfoItem = createInfoItem(mContext);
         }
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
index 2fe54c0..9b95bff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -19,7 +19,6 @@
 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;
 
@@ -138,10 +137,10 @@
         /**
          * Callback for when there is an inflation exception
          *
-         * @param notification notification which failed to inflate content
+         * @param entry notification which failed to inflate content
          * @param e exception
          */
-        void handleInflationException(StatusBarNotification notification, Exception e);
+        void handleInflationException(NotificationEntry entry, Exception e);
 
         /**
          * Callback for after the content views finish inflating.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
new file mode 100644
index 0000000..df8653c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import javax.inject.Singleton;
+
+import dagger.Binds;
+import dagger.Module;
+
+/**
+ * Dagger Module containing notification row and view inflation implementations.
+ */
+@Module
+public abstract class NotificationRowModule {
+    /**
+     * Provides notification row content binder instance.
+     */
+    @Binds
+    @Singleton
+    public abstract NotificationRowContentBinder provideNotificationRowContentBinder(
+            NotificationContentInflater contentBinderImpl);
+
+    /**
+     * Provides notification remote view cache instance.
+     */
+    @Binds
+    @Singleton
+    public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
+            NotifRemoteViewCacheImpl cacheImpl);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 352ba0f..76fdfc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -28,6 +28,7 @@
 import android.media.session.PlaybackState;
 import android.metrics.LogMaker;
 import android.os.Handler;
+import android.service.notification.StatusBarNotification;
 import android.text.format.DateUtils;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -176,27 +177,30 @@
         final MediaSession.Token token = mRow.getEntry().getSbn().getNotification().extras
                 .getParcelable(Notification.EXTRA_MEDIA_SESSION);
 
-        if (Utils.useQsMediaPlayer(mContext)) {
+        if (Utils.useQsMediaPlayer(mContext) && token != null) {
             final int[] compactActions = mRow.getEntry().getSbn().getNotification().extras
                     .getIntArray(Notification.EXTRA_COMPACT_ACTIONS);
             int tintColor = getNotificationHeader().getOriginalIconColor();
             StatusBarWindowController ctrl = Dependency.get(StatusBarWindowController.class);
             QuickQSPanel panel = ctrl.getStatusBarView().findViewById(
                     com.android.systemui.R.id.quick_qs_panel);
+            StatusBarNotification sbn = mRow.getEntry().getSbn();
+            Notification notif = sbn.getNotification();
             panel.getMediaPlayer().setMediaSession(token,
-                    mRow.getEntry().getSbn().getNotification().getSmallIcon(),
+                    notif.getSmallIcon(),
                     tintColor,
                     mBackgroundColor,
                     mActions,
-                    compactActions);
+                    compactActions,
+                    notif.contentIntent);
             QSPanel bigPanel = ctrl.getStatusBarView().findViewById(
                     com.android.systemui.R.id.quick_settings_panel);
             bigPanel.addMediaSession(token,
-                    mRow.getEntry().getSbn().getNotification().getSmallIcon(),
+                    notif.getSmallIcon(),
                     tintColor,
                     mBackgroundColor,
                     mActions,
-                    mRow.getEntry().getSbn());
+                    sbn);
         }
 
         boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index 2761689..09c1fad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -187,18 +187,18 @@
     @Override
     public boolean beginsSection(@NonNull View view, @Nullable View previous) {
         boolean begin = false;
-        if (view instanceof ExpandableNotificationRow) {
-            if (previous instanceof ExpandableNotificationRow) {
+        if (view instanceof ActivatableNotificationView) {
+            if (previous instanceof ActivatableNotificationView) {
                 // If we're drawing the first non-person notification, break out a section
-                ExpandableNotificationRow curr = (ExpandableNotificationRow) view;
-                ExpandableNotificationRow prev = (ExpandableNotificationRow) previous;
+                ActivatableNotificationView curr = (ActivatableNotificationView) view;
+                ActivatableNotificationView prev = (ActivatableNotificationView) previous;
 
-                begin =  curr.getEntry().getBucket() != prev.getEntry().getBucket();
+                begin = getBucket(curr) != getBucket(prev);
             }
         }
 
         if (!begin) {
-            begin = view == mGentleHeader || previous == mPeopleHubView;
+            begin = view == mGentleHeader || view == mPeopleHubView;
         }
 
         return begin;
@@ -230,29 +230,42 @@
             return;
         }
 
-        int lastPersonIndex = -1;
-        int firstGentleNotifIndex = -1;
+        boolean peopleNotificationsPresent = false;
+        int firstNonHeadsUpIndex = -1;
+        int firstGentleIndex = -1;
+        int notifCount = 0;
 
         final int n = mParent.getChildCount();
         for (int i = 0; i < n; i++) {
             View child = mParent.getChildAt(i);
-            if (child instanceof ExpandableNotificationRow
-                    && child.getVisibility() != View.GONE) {
+            if (child instanceof ExpandableNotificationRow && child.getVisibility() != View.GONE) {
+                notifCount++;
                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+                if (firstNonHeadsUpIndex == -1 && !row.isHeadsUp()) {
+                    firstNonHeadsUpIndex = i;
+                }
                 if (row.getEntry().getBucket() == BUCKET_PEOPLE) {
-                    lastPersonIndex = i;
+                    peopleNotificationsPresent = true;
                 }
                 if (row.getEntry().getBucket() == BUCKET_SILENT) {
-                    firstGentleNotifIndex = i;
+                    firstGentleIndex = i;
                     break;
                 }
             }
         }
 
-        // make room for peopleHub
-        firstGentleNotifIndex += adjustPeopleHubVisibilityAndPosition(lastPersonIndex);
+        if (firstNonHeadsUpIndex == -1) {
+            firstNonHeadsUpIndex = firstGentleIndex != -1 ? firstGentleIndex : notifCount;
+        }
 
-        adjustGentleHeaderVisibilityAndPosition(firstGentleNotifIndex);
+        // make room for peopleHub
+        int offset = adjustPeopleHubVisibilityAndPosition(
+                firstNonHeadsUpIndex, peopleNotificationsPresent);
+        if (firstGentleIndex != -1) {
+            firstGentleIndex += offset;
+        }
+
+        adjustGentleHeaderVisibilityAndPosition(firstGentleIndex);
 
         mGentleHeader.setAreThereDismissableGentleNotifs(
                 mParent.hasActiveClearableNotifications(ROWS_GENTLE));
@@ -294,13 +307,15 @@
         }
     }
 
-    private int adjustPeopleHubVisibilityAndPosition(int lastPersonIndex) {
-        final boolean showPeopleHeader = mPeopleHubVisible
-                && mNumberOfSections > 2
-                && mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
+    private int adjustPeopleHubVisibilityAndPosition(
+            int targetIndex, boolean peopleNotificationsPresent) {
+        final boolean showPeopleHeader = mNumberOfSections > 2
+                && mStatusBarStateController.getState() != StatusBarState.KEYGUARD
+                && (peopleNotificationsPresent || mPeopleHubVisible);
         final int currentHubIndex = mParent.indexOfChild(mPeopleHubView);
         final boolean currentlyVisible = currentHubIndex >= 0;
-        int targetIndex = lastPersonIndex + 1;
+
+        mPeopleHubView.setCanSwipe(showPeopleHeader && !peopleNotificationsPresent);
 
         if (!showPeopleHeader) {
             if (currentlyVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 6d0fcc3..4845ea1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -31,6 +31,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 
 class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeActionHelper {
@@ -298,8 +299,8 @@
     @Override
     public Animator getViewTranslationAnimator(View v, float target,
             ValueAnimator.AnimatorUpdateListener listener) {
-        if (v instanceof SwipeableView) {
-            return ((SwipeableView) v).getTranslateViewAnimator(target, listener);
+        if (v instanceof ExpandableNotificationRow) {
+            return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener);
         } else {
             return superGetViewTranslationAnimator(v, target, listener);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
index a079606..e5717ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
@@ -16,38 +16,22 @@
 
 package com.android.systemui.statusbar.notification.stack
 
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.ObjectAnimator
-import android.animation.ValueAnimator
 import android.content.Context
 import android.util.AttributeSet
-import android.util.FloatProperty
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
-import android.widget.LinearLayout
-import android.widget.TextView
 import com.android.systemui.R
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin
 import com.android.systemui.statusbar.notification.people.DataListener
 import com.android.systemui.statusbar.notification.people.PersonViewModel
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
 
-private val TRANSLATE_CONTENT = object : FloatProperty<PeopleHubView>("translate") {
-    override fun setValue(view: PeopleHubView, value: Float) {
-        view.translation = value
-    }
-
-    override fun get(view: PeopleHubView) = view.translation
-}
-
 class PeopleHubView(context: Context, attrs: AttributeSet) :
         ActivatableNotificationView(context, attrs), SwipeableView {
 
     private lateinit var contents: ViewGroup
     private lateinit var personControllers: List<PersonDataListenerImpl>
-    private var translateAnim: ObjectAnimator? = null
 
     val personViewAdapters: Sequence<DataListener<PersonViewModel?>>
         get() = personControllers.asSequence()
@@ -56,9 +40,10 @@
         super.onFinishInflate()
         contents = requireViewById(R.id.people_list)
         personControllers = (0 until contents.childCount)
+                .reversed()
                 .asSequence()
                 .mapNotNull { idx ->
-                    (contents.getChildAt(idx) as? LinearLayout)?.let(::PersonDataListenerImpl)
+                    (contents.getChildAt(idx) as? ImageView)?.let(::PersonDataListenerImpl)
                 }
                 .toList()
     }
@@ -69,41 +54,32 @@
 
     override fun createMenu(): NotificationMenuRowPlugin? = null
 
-    override fun getTranslateViewAnimator(
-        leftTarget: Float,
-        listener: ValueAnimator.AnimatorUpdateListener?
-    ): Animator =
-            ObjectAnimator
-                    .ofFloat(this, TRANSLATE_CONTENT, leftTarget)
-                    .apply {
-                        listener?.let { addUpdateListener(listener) }
-                        addListener(object : AnimatorListenerAdapter() {
-                            override fun onAnimationEnd(anim: Animator) {
-                                translateAnim = null
-                            }
-                        })
-                    }
-                    .also {
-                        translateAnim?.cancel()
-                        translateAnim = it
-                    }
-
     override fun resetTranslation() {
-        translateAnim?.cancel()
         translationX = 0f
     }
 
-    private inner class PersonDataListenerImpl(val viewGroup: ViewGroup) :
+    override fun setTranslation(translation: Float) {
+        if (canSwipe) {
+            super.setTranslation(translation)
+        }
+    }
+
+    var canSwipe: Boolean = true
+        set(value) {
+            if (field != value) {
+                if (field) {
+                    resetTranslation()
+                }
+                field = value
+            }
+        }
+
+    private inner class PersonDataListenerImpl(val avatarView: ImageView) :
             DataListener<PersonViewModel?> {
 
-        val nameView = viewGroup.requireViewById<TextView>(R.id.person_name)
-        val avatarView = viewGroup.requireViewById<ImageView>(R.id.person_icon)
-
         override fun onDataChanged(data: PersonViewModel?) {
-            viewGroup.visibility = data?.let { View.VISIBLE } ?: View.INVISIBLE
-            nameView.text = data?.name
             avatarView.setImageDrawable(data?.icon)
-            viewGroup.setOnClickListener { data?.onClick?.invoke() }
+            avatarView.setOnClickListener { data?.onClick?.invoke() }
         }
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SwipeableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SwipeableView.java
index 6c6ef61..49e59a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SwipeableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SwipeableView.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
-import android.animation.Animator;
-import android.animation.ValueAnimator;
-
 import androidx.annotation.Nullable;
 
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -32,10 +29,6 @@
     /** Optionally creates a menu for this view. */
     @Nullable NotificationMenuRowPlugin createMenu();
 
-    /** Animator for translating the view, simulating a swipe. */
-    Animator getTranslateViewAnimator(
-            float leftTarget, ValueAnimator.AnimatorUpdateListener listener);
-
     /** Sets the translation amount for an in-progress swipe. */
     void setTranslation(float translate);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index f7d52b5..ad1aa83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -98,7 +98,12 @@
                 bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
             }
         }, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
-        lockscreenUserManager.addUserChangedListener { pendingUnlockType = null }
+        lockscreenUserManager.addUserChangedListener(
+                object : NotificationLockscreenUserManager.UserChangedListener {
+                    override fun onUserChanged(userId: Int) {
+                        pendingUnlockType = null
+                    }
+                })
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index a3b1b5f..a6842ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -366,8 +366,8 @@
         IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
         filter.addAction(Intent.ACTION_SCREEN_ON);
         filter.addAction(Intent.ACTION_USER_SWITCHED);
-        mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, Handler.getMain(),
-                UserHandle.ALL);
+        mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter,
+                Handler.getMain(), UserHandle.ALL);
         notifyNavigationBarScreenOn();
 
         mOverviewProxyService.addCallback(mOverviewProxyListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index fe0739f..896b6e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -32,7 +32,6 @@
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.AsyncInflationTask;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
@@ -428,7 +427,7 @@
          * The notification is still pending inflation but we've decided that we no longer need
          * the content view (e.g. suppression might have changed and we decided we need to transfer
          * back). However, there is no way to abort just this inflation if other inflation requests
-         * have started (see {@link AsyncInflationTask#supersedeTask(InflationTask)}). So instead
+         * have started (see {@link InflationTask#supersedeTask(InflationTask)}). So instead
          * we just flag it as aborted and free when it's inflated.
          */
         boolean mAbortOnInflation;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 9dc8fbf..0f3af09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -24,8 +24,6 @@
 import android.graphics.PorterDuffXfermode;
 import android.util.AttributeSet;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 public class NotificationPanelView extends PanelView {
 
     private static final boolean DEBUG = false;
@@ -38,14 +36,10 @@
     static final String COUNTER_PANEL_OPEN = "panel_open";
     static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
 
-    @VisibleForTesting
-    protected KeyguardAffordanceHelper mAffordanceHelper;
-
-    private int mOldLayoutDirection;
-
     private int mCurrentPanelAlpha;
     private final Paint mAlphaPaint = new Paint();
     private boolean mDozing;
+    private RtlChangeListener mRtlChangeListener;
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -57,9 +51,8 @@
 
     @Override
     public void onRtlPropertiesChanged(int layoutDirection) {
-        if (layoutDirection != mOldLayoutDirection) {
-            mAffordanceHelper.onRtlPropertiesChanged();
-            mOldLayoutDirection = layoutDirection;
+        if (mRtlChangeListener != null) {
+            mRtlChangeListener.onRtlPropertielsChanged(layoutDirection);
         }
     }
 
@@ -95,4 +88,11 @@
         return !mDozing;
     }
 
+    void setRtlChangeListener(RtlChangeListener listener) {
+        mRtlChangeListener = listener;
+    }
+
+    interface RtlChangeListener {
+        void onRtlPropertielsChanged(int layoutDirection);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index bbde25b..90ec2a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -449,6 +449,7 @@
     private PluginManager mPluginManager;
     private FrameLayout mPluginFrame;
     private NPVPluginManager mNPVPluginManager;
+    private int mOldLayoutDirection;
 
     @Inject
     public NotificationPanelViewController(NotificationPanelView view,
@@ -603,6 +604,13 @@
 
             }
         }, HomeControlsPlugin.class, false);
+
+        mView.setRtlChangeListener(layoutDirection -> {
+            if (layoutDirection != mOldLayoutDirection) {
+                mAffordanceHelper.onRtlPropertiesChanged();
+                mOldLayoutDirection = layoutDirection;
+            }
+        });
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 5b34aa7..00e38f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -179,7 +179,7 @@
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
-        broadcastDispatcher.registerReceiver(mIntentReceiver, filter, mHandler);
+        broadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);
 
         // listen for user / profile change.
         try {
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 277a761..189d3b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -29,7 +29,6 @@
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
 
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
@@ -365,7 +364,6 @@
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final BypassHeadsUpNotifier mBypassHeadsUpNotifier;
-    private final boolean mAllowNotificationLongPress;
     private final Lazy<NewNotifPipeline> mNewNotifPipeline;
     private final FalsingManager mFalsingManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
@@ -388,6 +386,7 @@
     private final KeyguardDismissUtil mKeyguardDismissUtil;
     private final ExtensionController mExtensionController;
     private final UserInfoControllerImpl mUserInfoControllerImpl;
+    private final NotificationRowBinderImpl mNotificationRowBinder;
     private final DismissCallbackRegistry mDismissCallbackRegistry;
 
     // expanded notifications
@@ -626,7 +625,6 @@
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
             BypassHeadsUpNotifier bypassHeadsUpNotifier,
-            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
             Lazy<NewNotifPipeline> newNotifPipeline,
             FalsingManager falsingManager,
             BroadcastDispatcher broadcastDispatcher,
@@ -693,6 +691,7 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
+            NotificationRowBinderImpl notificationRowBinder,
             DismissCallbackRegistry dismissCallbackRegistry) {
         super(context);
         mFeatureFlags = featureFlags;
@@ -707,7 +706,6 @@
         mHeadsUpManager = headsUpManagerPhone;
         mDynamicPrivacyController = dynamicPrivacyController;
         mBypassHeadsUpNotifier = bypassHeadsUpNotifier;
-        mAllowNotificationLongPress = allowNotificationLongPress;
         mNewNotifPipeline = newNotifPipeline;
         mFalsingManager = falsingManager;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -772,6 +770,7 @@
         mKeyguardDismissUtil = keyguardDismissUtil;
         mExtensionController = extensionController;
         mUserInfoControllerImpl = userInfoControllerImpl;
+        mNotificationRowBinder = notificationRowBinder;
         mDismissCallbackRegistry = dismissCallbackRegistry;
 
         mBubbleExpandListener =
@@ -1237,19 +1236,11 @@
                 mStatusBarWindowViewController, this, mNotificationPanelViewController,
                 (NotificationListContainer) mStackScroller);
 
-        final NotificationRowBinderImpl rowBinder =
-                new NotificationRowBinderImpl(
-                        mContext,
-                        mAllowNotificationLongPress,
-                        mKeyguardBypassController,
-                        mStatusBarStateController,
-                        mNotificationLogger);
-
         // TODO: inject this.
         mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController,
                 mHeadsUpManager, mStatusBarWindow, mStackScroller, mDozeScrimController,
                 mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController,
-                mNotificationAlertingManager, rowBinder, mKeyguardStateController,
+                mNotificationAlertingManager, mNotificationRowBinder, mKeyguardStateController,
                 mKeyguardIndicationController,
                 this /* statusBar */, mShadeController, mCommandQueue, mInitController);
 
@@ -1272,17 +1263,20 @@
 
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
 
-        mEntryManager.setRowBinder(rowBinder);
+        if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mNotificationRowBinder.setInflationCallback(mEntryManager);
+        }
+
         mRemoteInputUriController.attach(mEntryManager);
 
-        rowBinder.setNotificationClicker(new NotificationClicker(
+        mNotificationRowBinder.setNotificationClicker(new NotificationClicker(
                 Optional.of(this), mBubbleController, mNotificationActivityStarter));
 
         mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager);
         mNotificationListController.bind();
 
         if (mFeatureFlags.isNewNotifPipelineEnabled()) {
-            mNewNotifPipeline.get().initialize(mNotificationListener);
+            mNewNotifPipeline.get().initialize(mNotificationListener, mNotificationRowBinder);
         }
         mEntryManager.attach(mNotificationListener);
     }
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 153ca22..be7f0a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
 import android.content.Context;
@@ -66,6 +65,7 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -117,7 +117,6 @@
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
             BypassHeadsUpNotifier bypassHeadsUpNotifier,
-            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
             Lazy<NewNotifPipeline> newNotifPipeline,
             FalsingManager falsingManager,
             BroadcastDispatcher broadcastDispatcher,
@@ -184,6 +183,7 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
+            NotificationRowBinderImpl notificationRowBinder,
             DismissCallbackRegistry dismissCallbackRegistry) {
         return new StatusBar(
                 context,
@@ -199,7 +199,6 @@
                 headsUpManagerPhone,
                 dynamicPrivacyController,
                 bypassHeadsUpNotifier,
-                allowNotificationLongPress,
                 newNotifPipeline,
                 falsingManager,
                 broadcastDispatcher,
@@ -265,6 +264,7 @@
                 keyguardDismissUtil,
                 extensionController,
                 userInfoControllerImpl,
+                notificationRowBinder,
                 dismissCallbackRegistry);
     }
 }
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 0fd0dab..12a6516 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -217,7 +217,7 @@
             mEntryManager.addNotificationLifetimeExtenders(
                     remoteInputManager.getLifetimeExtenders());
             notificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager,
-                    mEntryManager, this);
+                    this);
             mNotificationInterruptionStateProvider.setUpWithPresenter(
                     this, mHeadsUpManager, this::canHeadsUp);
             mLockscreenUserManager.setUpWithPresenter(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index ddacc3a..4f0af9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -188,7 +188,7 @@
             // NOTE: This receiver could run before this method returns, as it's not dispatching
             // on the main thread and BroadcastDispatcher may not need to register with Context.
             // The receiver will return immediately if the view does not have a Handler yet.
-            mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter,
+            mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter,
                     Dependency.get(Dependency.TIME_TICK_HANDLER), UserHandle.ALL);
             Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
                     StatusBarIconController.ICON_BLACKLIST);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
index 2e26711..b4c154a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
@@ -96,7 +96,7 @@
         filter.addAction(Intent.ACTION_TIME_CHANGED);
         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
         filter.addAction(Intent.ACTION_LOCALE_CHANGED);
-        mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter,
+        mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter,
                 Dependency.get(Dependency.TIME_TICK_HANDLER));
 
         updateClock();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 570f153..cb40d77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -79,7 +79,8 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
         filter.addAction(LocationManager.MODE_CHANGED_ACTION);
-        mBroadcastDispatcher.registerReceiver(this, filter, new Handler(bgLooper), UserHandle.ALL);
+        mBroadcastDispatcher.registerReceiverWithHandler(this, filter,
+                new Handler(bgLooper), UserHandle.ALL);
 
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mStatusBarManager
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 6b842d5..5916180 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -654,7 +654,7 @@
     }
 
     boolean isDataDisabled() {
-        return !mPhone.isDataCapable();
+        return !mPhone.isDataConnectionEnabled();
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index f640d03..679fa7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -326,7 +326,7 @@
         filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
         filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        mBroadcastDispatcher.registerReceiver(this, filter, mReceiverHandler);
+        mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
         mListening = true;
 
         updateMobileControllers();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 019ef3b..312c4ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -126,7 +126,8 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
-        broadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, bgHandler, UserHandle.ALL);
+        broadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, bgHandler,
+                UserHandle.ALL);
 
         // TODO: re-register network callback on user change.
         mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 7758aba..31b9952 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -88,7 +88,7 @@
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
-        mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() {
+        mBroadcastDispatcher.registerReceiverWithHandler(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
index 7cdba86..cc6d607 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
@@ -83,17 +83,19 @@
      * Provide a Background-Thread Executor by default.
      */
     @Provides
+    @Singleton
     public static Executor provideExecutor(@Background Looper looper) {
-        return new ExecutorImpl(new Handler(looper));
+        return new ExecutorImpl(looper);
     }
 
     /**
      * Provide a Background-Thread Executor.
      */
     @Provides
+    @Singleton
     @Background
     public static Executor provideBackgroundExecutor(@Background Looper looper) {
-        return new ExecutorImpl(new Handler(looper));
+        return new ExecutorImpl(looper);
     }
 
     /**
@@ -109,26 +111,29 @@
      * Provide a Background-Thread Executor by default.
      */
     @Provides
+    @Singleton
     public static DelayableExecutor provideDelayableExecutor(@Background Looper looper) {
-        return new ExecutorImpl(new Handler(looper));
+        return new ExecutorImpl(looper);
     }
 
     /**
      * Provide a Background-Thread Executor.
      */
     @Provides
+    @Singleton
     @Background
     public static DelayableExecutor provideBackgroundDelayableExecutor(@Background Looper looper) {
-        return new ExecutorImpl(new Handler(looper));
+        return new ExecutorImpl(looper);
     }
 
     /**
      * Provide a Main-Thread Executor.
      */
     @Provides
+    @Singleton
     @Main
     public static DelayableExecutor provideMainDelayableExecutor(@Main Looper looper) {
-        return new ExecutorImpl(new Handler(looper));
+        return new ExecutorImpl(looper);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java
index 7e77321..2bbf950 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java
@@ -17,37 +17,69 @@
 package com.android.systemui.util.concurrency;
 
 import android.os.Handler;
-import android.os.HandlerExecutor;
+import android.os.Looper;
 import android.os.Message;
 
+import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.TimeUnit;
 
 /**
  * Implementations of {@link DelayableExecutor} for SystemUI.
  */
-public class ExecutorImpl extends HandlerExecutor implements DelayableExecutor {
+public class ExecutorImpl implements DelayableExecutor {
     private final Handler mHandler;
 
-    public ExecutorImpl(Handler handler) {
-        super(handler);
-        mHandler = handler;
+    ExecutorImpl(Looper looper) {
+        mHandler = new Handler(looper, this::onHandleMessage);
+    }
+
+    @Override
+    public void execute(Runnable command) {
+        if (!mHandler.post(command)) {
+            throw new RejectedExecutionException(mHandler + " is shutting down");
+        }
     }
 
     @Override
     public Runnable executeDelayed(Runnable r, long delay, TimeUnit unit) {
-        Object token = new Object();
-        Message m = mHandler.obtainMessage(0, token);
+        ExecutionToken token = new ExecutionToken(r);
+        Message m = mHandler.obtainMessage(MSG_EXECUTE_RUNNABLE, token);
         mHandler.sendMessageDelayed(m, unit.toMillis(delay));
 
-        return () -> mHandler.removeCallbacksAndMessages(token);
+        return token;
     }
 
     @Override
     public Runnable executeAtTime(Runnable r, long uptimeMillis, TimeUnit unit) {
-        Object token = new Object();
-        Message m = mHandler.obtainMessage(0, token);
+        ExecutionToken token = new ExecutionToken(r);
+        Message m = mHandler.obtainMessage(MSG_EXECUTE_RUNNABLE, token);
         mHandler.sendMessageAtTime(m, unit.toMillis(uptimeMillis));
 
-        return () -> mHandler.removeCallbacksAndMessages(token);
+        return token;
     }
+
+    private boolean onHandleMessage(Message msg) {
+        if (msg.what == MSG_EXECUTE_RUNNABLE) {
+            ExecutionToken token = (ExecutionToken) msg.obj;
+            token.runnable.run();
+        } else {
+            throw new IllegalStateException("Unrecognized message: " + msg.what);
+        }
+        return true;
+    }
+
+    private class ExecutionToken implements Runnable {
+        public final Runnable runnable;
+
+        private ExecutionToken(Runnable runnable) {
+            this.runnable = runnable;
+        }
+
+        @Override
+        public void run() {
+            mHandler.removeCallbacksAndMessages(this);
+        }
+    }
+
+    private static final int MSG_EXECUTE_RUNNABLE = 0;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index a4ed31d..112ae6f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -1008,7 +1008,7 @@
             filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
             filter.addAction(Intent.ACTION_SCREEN_OFF);
             filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
-            mBroadcastDispatcher.registerReceiver(this, filter, mWorker);
+            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mWorker);
         }
 
         public void destroy() {
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
new file mode 100644
index 0000000..d413308
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wm;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.IDisplayWindowInsetsController;
+import android.view.InsetsSource;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.WindowInsets;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Manages IME control at the display-level. This occurs when IME comes up in multi-window mode.
+ */
+@Singleton
+public class DisplayImeController implements DisplayWindowController.DisplayWindowListener {
+    private static final String TAG = "DisplayImeController";
+
+    static final int ANIMATION_DURATION_SHOW_MS = 275;
+    static final int ANIMATION_DURATION_HIDE_MS = 340;
+    static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+    private static final int DIRECTION_NONE = 0;
+    private static final int DIRECTION_SHOW = 1;
+    private static final int DIRECTION_HIDE = 2;
+
+    SystemWindows mSystemWindows;
+    final Handler mHandler;
+
+    final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>();
+
+    final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>();
+
+    @Inject
+    DisplayImeController(SystemWindows syswin, DisplayWindowController displayController,
+            @Main Handler mainHandler) {
+        mHandler = mainHandler;
+        mSystemWindows = syswin;
+        displayController.addDisplayWindowListener(this);
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) {
+        // Add's a system-ui window-manager specifically for ime. This type is special because
+        // WM will defer IME inset handling to it in multi-window scenarious.
+        PerDisplay pd = new PerDisplay(displayId,
+                mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation());
+        try {
+            mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Unable to set insets controller on display " + displayId);
+        }
+        mImePerDisplay.put(displayId, pd);
+    }
+
+    @Override
+    public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+        PerDisplay pd = mImePerDisplay.get(displayId);
+        if (pd == null) {
+            return;
+        }
+        if (mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation()
+                != pd.mRotation && isImeShowing(displayId)) {
+            pd.startAnimation(true);
+        }
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+        try {
+            mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Unable to remove insets controller on display " + displayId);
+        }
+        mImePerDisplay.remove(displayId);
+    }
+
+    private boolean isImeShowing(int displayId) {
+        PerDisplay pd = mImePerDisplay.get(displayId);
+        if (pd == null) {
+            return false;
+        }
+        final InsetsSource imeSource = pd.mInsetsState.getSource(InsetsState.ITYPE_IME);
+        return imeSource != null && pd.mImeSourceControl != null && imeSource.isVisible();
+    }
+
+    private void dispatchPositionChanged(int displayId, int imeTop,
+            SurfaceControl.Transaction t) {
+        synchronized (mPositionProcessors) {
+            for (ImePositionProcessor pp : mPositionProcessors) {
+                pp.onImePositionChanged(displayId, imeTop, t);
+            }
+        }
+    }
+
+    private void dispatchStartPositioning(int displayId, int imeTop, int finalImeTop,
+            boolean show, SurfaceControl.Transaction t) {
+        synchronized (mPositionProcessors) {
+            for (ImePositionProcessor pp : mPositionProcessors) {
+                pp.onImeStartPositioning(displayId, imeTop, finalImeTop, show, t);
+            }
+        }
+    }
+
+    private void dispatchEndPositioning(int displayId, int imeTop, boolean show,
+            SurfaceControl.Transaction t) {
+        synchronized (mPositionProcessors) {
+            for (ImePositionProcessor pp : mPositionProcessors) {
+                pp.onImeEndPositioning(displayId, imeTop, show, t);
+            }
+        }
+    }
+
+    /**
+     * Adds an {@link ImePositionProcessor} to be called during ime position updates.
+     */
+    public void addPositionProcessor(ImePositionProcessor processor) {
+        synchronized (mPositionProcessors) {
+            if (mPositionProcessors.contains(processor)) {
+                return;
+            }
+            mPositionProcessors.add(processor);
+        }
+    }
+
+    /**
+     * Removes an {@link ImePositionProcessor} to be called during ime position updates.
+     */
+    public void removePositionProcessor(ImePositionProcessor processor) {
+        synchronized (mPositionProcessors) {
+            mPositionProcessors.remove(processor);
+        }
+    }
+
+    class PerDisplay extends IDisplayWindowInsetsController.Stub {
+        final int mDisplayId;
+        final InsetsState mInsetsState = new InsetsState();
+        InsetsSourceControl mImeSourceControl = null;
+        int mAnimationDirection = DIRECTION_NONE;
+        ValueAnimator mAnimation = null;
+        int mRotation = Surface.ROTATION_0;
+
+        PerDisplay(int displayId, int initialRotation) {
+            mDisplayId = displayId;
+            mRotation = initialRotation;
+        }
+
+        @Override
+        public void insetsChanged(InsetsState insetsState) {
+            if (mInsetsState.equals(insetsState)) {
+                return;
+            }
+            mInsetsState.set(insetsState, true /* copySources */);
+        }
+
+        @Override
+        public void insetsControlChanged(InsetsState insetsState,
+                InsetsSourceControl[] activeControls) {
+            insetsChanged(insetsState);
+            if (activeControls != null) {
+                for (InsetsSourceControl activeControl : activeControls) {
+                    if (activeControl == null) {
+                        continue;
+                    }
+                    if (activeControl.getType() == InsetsState.ITYPE_IME) {
+                        mImeSourceControl = activeControl;
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void showInsets(int types, boolean fromIme) {
+            if ((types & WindowInsets.Type.ime()) == 0) {
+                return;
+            }
+            startAnimation(true /* show */);
+        }
+
+        @Override
+        public void hideInsets(int types, boolean fromIme) {
+            if ((types & WindowInsets.Type.ime()) == 0) {
+                return;
+            }
+            startAnimation(false /* show */);
+        }
+
+        /**
+         * Sends the local visibility state back to window manager. Needed for legacy adjustForIme.
+         */
+        private void setVisibleDirectly(boolean visible) {
+            mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible);
+            try {
+                mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState);
+            } catch (RemoteException e) {
+            }
+        }
+
+        private int imeTop(InsetsSource imeSource, float surfaceOffset) {
+            return imeSource.getFrame().top + (int) surfaceOffset;
+        }
+
+        private void startAnimation(final boolean show) {
+            final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME);
+            if (imeSource == null || mImeSourceControl == null) {
+                return;
+            }
+            if ((mAnimationDirection == DIRECTION_SHOW && show)
+                    || (mAnimationDirection == DIRECTION_HIDE && !show)) {
+                return;
+            }
+            if (mAnimationDirection != DIRECTION_NONE) {
+                mAnimation.end();
+                mAnimationDirection = DIRECTION_NONE;
+            }
+            mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE;
+            mHandler.post(() -> {
+                final float defaultY = mImeSourceControl.getSurfacePosition().y;
+                final float x = mImeSourceControl.getSurfacePosition().x;
+                final float startY = show ? defaultY + imeSource.getFrame().height() : defaultY;
+                final float endY = show ? defaultY : defaultY + imeSource.getFrame().height();
+                mAnimation = ValueAnimator.ofFloat(startY, endY);
+                mAnimation.setDuration(
+                        show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS);
+
+                mAnimation.addUpdateListener(animation -> {
+                    SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+                    float value = (float) animation.getAnimatedValue();
+                    t.setPosition(mImeSourceControl.getLeash(), x, value);
+                    dispatchPositionChanged(mDisplayId, imeTop(imeSource, value), t);
+                    t.apply();
+                    t.close();
+                });
+                mAnimation.setInterpolator(INTERPOLATOR);
+                mAnimation.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationStart(Animator animation) {
+                        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+                        t.setPosition(mImeSourceControl.getLeash(), x, startY);
+                        dispatchStartPositioning(mDisplayId, imeTop(imeSource, startY),
+                                imeTop(imeSource, endY), mAnimationDirection == DIRECTION_SHOW,
+                                t);
+                        if (mAnimationDirection == DIRECTION_SHOW) {
+                            t.show(mImeSourceControl.getLeash());
+                        }
+                        t.apply();
+                        t.close();
+                    }
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+                        t.setPosition(mImeSourceControl.getLeash(), x, endY);
+                        dispatchEndPositioning(mDisplayId, imeTop(imeSource, endY),
+                                mAnimationDirection == DIRECTION_SHOW, t);
+                        if (mAnimationDirection == DIRECTION_HIDE) {
+                            t.hide(mImeSourceControl.getLeash());
+                        }
+                        t.apply();
+                        t.close();
+
+                        mAnimationDirection = DIRECTION_NONE;
+                        mAnimation = null;
+                    }
+                });
+                if (!show) {
+                    // When going away, queue up insets change first, otherwise any bounds changes
+                    // can have a "flicker" of ime-provided insets.
+                    setVisibleDirectly(false /* visible */);
+                }
+                mAnimation.start();
+                if (show) {
+                    // When showing away, queue up insets change last, otherwise any bounds changes
+                    // can have a "flicker" of ime-provided insets.
+                    setVisibleDirectly(true /* visible */);
+                }
+            });
+        }
+    }
+
+    /**
+     * Allows other things to synchronize with the ime position
+     */
+    public interface ImePositionProcessor {
+        /**
+         * Called when the IME position is starting to animate.
+         */
+        void onImeStartPositioning(int displayId, int imeTop, int finalImeTop, boolean showing,
+                SurfaceControl.Transaction t);
+
+        /**
+         * Called when the ime position changed. This is expected to be a synchronous call on the
+         * animation thread. Operations can be added to the transaction to be applied in sync.
+         */
+        void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t);
+
+        /**
+         * Called when the IME position is done animating.
+         */
+        void onImeEndPositioning(int displayId, int imeTop, boolean showing,
+                SurfaceControl.Transaction t);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index b70fdbd..eccf096 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -19,7 +19,7 @@
 
 import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
 import static android.telephony.SubscriptionManager.DATA_ROAMING_ENABLE;
-import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE;
+import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT;
 
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.TestCase.assertFalse;
@@ -83,14 +83,14 @@
     private static final String TEST_CARRIER_2 = "TEST_CARRIER_2";
     private static final int TEST_CARRIER_ID = 1;
     private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(0, "", 0,
-            TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
+            TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT, 0xFFFFFF, "",
             DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, null,
             TEST_CARRIER_ID, 0);
     private static final SubscriptionInfo TEST_SUBSCRIPTION_NULL = new SubscriptionInfo(0, "", 0,
-            TEST_CARRIER, null, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "", DATA_ROAMING_DISABLE,
+            TEST_CARRIER, null, NAME_SOURCE_DEFAULT, 0xFFFFFF, "", DATA_ROAMING_DISABLE,
             null, null, null, null, false, null, "");
     private static final SubscriptionInfo TEST_SUBSCRIPTION_ROAMING = new SubscriptionInfo(0, "", 0,
-            TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
+            TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT, 0xFFFFFF, "",
             DATA_ROAMING_ENABLE, null, null, null, null, false, null, "");
     @Mock
     private WifiManager mWifiManager;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 40ea6dd..2e0fb3b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -17,7 +17,7 @@
 package com.android.keyguard;
 
 import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
-import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE;
+import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -84,11 +84,11 @@
     private static final int TEST_CARRIER_ID = 1;
     private static final String TEST_GROUP_UUID = "59b5c870-fc4c-47a4-a99e-9db826b48b24";
     private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(1, "", 0,
-            TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
+            TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT, 0xFFFFFF, "",
             DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID,
             TEST_CARRIER_ID, 0);
     private static final SubscriptionInfo TEST_SUBSCRIPTION_2 = new SubscriptionInfo(2, "", 0,
-            TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
+            TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_DEFAULT, 0xFFFFFF, "",
             DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", true, TEST_GROUP_UUID,
             TEST_CARRIER_ID, 0);
     @Mock
@@ -150,10 +150,10 @@
 
     @Test
     public void testReceiversRegistered() {
-        verify(mBroadcastDispatcher, atLeastOnce()).registerReceiver(
+        verify(mBroadcastDispatcher, atLeastOnce()).registerReceiverWithHandler(
                 eq(mKeyguardUpdateMonitor.mBroadcastReceiver),
                 any(IntentFilter.class), any(Handler.class));
-        verify(mBroadcastDispatcher, atLeastOnce()).registerReceiver(
+        verify(mBroadcastDispatcher, atLeastOnce()).registerReceiverWithHandler(
                 eq(mKeyguardUpdateMonitor.mBroadcastAllReceiver),
                 any(IntentFilter.class), any(Handler.class), eq(UserHandle.ALL));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
index 0c53b03..2ecc8ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
@@ -52,6 +52,7 @@
     @Override
     protected <T> T createDependency(Object key) {
         if (mObjs.containsKey(key)) return (T) mObjs.get(key);
+
         mInstantiatedObjects.add(key);
         return super.createDependency(key);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index af0ef82..e5ae1aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -25,11 +25,11 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+
+import static java.lang.Thread.sleep;
 
 import android.app.AppOpsManager;
 import android.content.pm.PackageManager;
@@ -57,7 +57,6 @@
     private static final String TEST_PACKAGE_NAME = "test";
     private static final int TEST_UID = UserHandle.getUid(0, 0);
     private static final int TEST_UID_OTHER = UserHandle.getUid(1, 0);
-    private static final int TEST_UID_NON_USER_SENSITIVE = UserHandle.getUid(2, 0);
 
     @Mock
     private AppOpsManager mAppOpsManager;
@@ -68,8 +67,6 @@
     @Mock
     private AppOpsControllerImpl.H mMockHandler;
     @Mock
-    private PermissionFlagsCache mFlagsCache;
-    @Mock
     private DumpController mDumpController;
 
     private AppOpsControllerImpl mController;
@@ -85,17 +82,9 @@
         // All permissions of TEST_UID and TEST_UID_OTHER are user sensitive. None of
         // TEST_UID_NON_USER_SENSITIVE are user sensitive.
         getContext().setMockPackageManager(mPackageManager);
-        when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
-                eq(UserHandle.getUserHandleForUid(TEST_UID)))).thenReturn(
-                PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED);
-        when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
-                eq(UserHandle.getUserHandleForUid(TEST_UID_OTHER)))).thenReturn(
-                PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED);
-        when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
-                eq(UserHandle.getUserHandleForUid(TEST_UID_NON_USER_SENSITIVE)))).thenReturn(0);
 
-        mController = new AppOpsControllerImpl(mContext, mTestableLooper.getLooper(), mFlagsCache,
-                mDumpController);
+        mController =
+                new AppOpsControllerImpl(mContext, mTestableLooper.getLooper(), mDumpController);
     }
 
     @Test
@@ -191,14 +180,6 @@
     }
 
     @Test
-    public void nonUserSensitiveOpsAreIgnored() {
-        mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
-                TEST_UID_NON_USER_SENSITIVE, TEST_PACKAGE_NAME, true);
-        assertEquals(0, mController.getActiveAppOpsForUser(
-                UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size());
-    }
-
-    @Test
     public void opNotedScheduledForRemoval() {
         mController.setBGHandler(mMockHandler);
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
@@ -251,4 +232,100 @@
         // Only one post to notify subscribers
         verify(mMockHandler, times(2)).scheduleRemoval(any(), anyLong());
     }
+
+    @Test
+    public void testActiveOpNotRemovedAfterNoted() throws InterruptedException {
+        // Replaces the timeout delay with 5 ms
+        AppOpsControllerImpl.H testHandler = mController.new H(mTestableLooper.getLooper()) {
+            @Override
+            public void scheduleRemoval(AppOpItem item, long timeToRemoval) {
+                super.scheduleRemoval(item, 5L);
+            }
+        };
+
+        mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+        mController.setBGHandler(testHandler);
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+        mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+                AppOpsManager.MODE_ALLOWED);
+
+        mTestableLooper.processAllMessages();
+        List<AppOpItem> list = mController.getActiveAppOps();
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+        // Duplicates are not removed between active and noted
+        assertEquals(2, list.size());
+
+        sleep(10L);
+
+        mTestableLooper.processAllMessages();
+
+        verify(mCallback, never()).onActiveStateChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
+        list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+    }
+
+    @Test
+    public void testNotedNotRemovedAfterActive() {
+        mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+
+        mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+                AppOpsManager.MODE_ALLOWED);
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+        mTestableLooper.processAllMessages();
+        List<AppOpItem> list = mController.getActiveAppOps();
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+        // Duplicates are not removed between active and noted
+        assertEquals(2, list.size());
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
+
+        mTestableLooper.processAllMessages();
+
+        verify(mCallback, never()).onActiveStateChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
+        list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+    }
+
+    @Test
+    public void testNotedAndActiveOnlyOneCall() {
+        mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+
+        mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+                AppOpsManager.MODE_ALLOWED);
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+        mTestableLooper.processAllMessages();
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+    }
+
+    @Test
+    public void testActiveAndNotedOnlyOneCall() {
+        mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+
+        mController.onOpActiveChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+        mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+                AppOpsManager.MODE_ALLOWED);
+
+        mTestableLooper.processAllMessages();
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt
deleted file mode 100644
index dc070de..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.appops
-
-import android.content.Context
-import android.content.pm.PackageManager
-import android.os.UserHandle
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.Mock
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class PermissionFlagsCacheTest : SysuiTestCase() {
-
-    companion object {
-        const val TEST_PERMISSION = "test_permission"
-        const val TEST_PACKAGE = "test_package"
-    }
-
-    @Mock
-    private lateinit var mPackageManager: PackageManager
-    @Mock
-    private lateinit var mUserHandle: UserHandle
-    private lateinit var flagsCache: TestPermissionFlagsCache
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        mContext.setMockPackageManager(mPackageManager)
-        flagsCache = TestPermissionFlagsCache(mContext)
-    }
-
-    @Test
-    fun testCallsPackageManager_exactlyOnce() {
-        flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
-        flagsCache.time = CACHE_EXPIRATION - 1
-        verify(mPackageManager).getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
-    }
-
-    @Test
-    fun testCallsPackageManager_cacheExpired() {
-        flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
-        flagsCache.time = CACHE_EXPIRATION + 1
-        flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
-        verify(mPackageManager, times(2))
-                .getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
-    }
-
-    @Test
-    fun testCallsPackageMaanger_multipleKeys() {
-        flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
-        flagsCache.getPermissionFlags(TEST_PERMISSION, "", mUserHandle)
-        verify(mPackageManager, times(2))
-                .getPermissionFlags(anyString(), anyString(), any())
-    }
-
-    private class TestPermissionFlagsCache(context: Context) : PermissionFlagsCache(context) {
-        var time = 0L
-
-        override fun getCurrentTime(): Long {
-            return time
-        }
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index 42fbf59..22b1837 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -27,6 +27,8 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
 import junit.framework.Assert.assertSame
 import org.junit.Before
 import org.junit.Test
@@ -39,6 +41,7 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
 
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
@@ -73,6 +76,8 @@
     @Mock
     private lateinit var mockHandler: Handler
 
+    private lateinit var executor: Executor
+
     @Captor
     private lateinit var argumentCaptor: ArgumentCaptor<ReceiverData>
 
@@ -83,6 +88,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
+        executor = FakeExecutor(FakeSystemClock())
 
         broadcastDispatcher = TestBroadcastDispatcher(
                 mockContext,
@@ -98,8 +104,9 @@
 
     @Test
     fun testAddingReceiverToCorrectUBR() {
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
-        broadcastDispatcher.registerReceiver(
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user0)
+        broadcastDispatcher.registerReceiverWithHandler(
                 broadcastReceiverOther, intentFilterOther, mockHandler, user1)
 
         testableLooper.processAllMessages()
@@ -115,9 +122,29 @@
     }
 
     @Test
+    fun testAddingReceiverToCorrectUBR_executor() {
+        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, executor, user0)
+        broadcastDispatcher.registerReceiver(
+                broadcastReceiverOther, intentFilterOther, executor, user1)
+
+        testableLooper.processAllMessages()
+
+        verify(mockUBRUser0).registerReceiver(capture(argumentCaptor))
+
+        assertSame(broadcastReceiver, argumentCaptor.value.receiver)
+        assertSame(intentFilter, argumentCaptor.value.filter)
+
+        verify(mockUBRUser1).registerReceiver(capture(argumentCaptor))
+        assertSame(broadcastReceiverOther, argumentCaptor.value.receiver)
+        assertSame(intentFilterOther, argumentCaptor.value.filter)
+    }
+
+    @Test
     fun testRemovingReceiversRemovesFromAllUBR() {
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user1)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user0)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user1)
 
         broadcastDispatcher.unregisterReceiver(broadcastReceiver)
 
@@ -129,8 +156,10 @@
 
     @Test
     fun testRemoveReceiverFromUser() {
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user1)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user0)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user1)
 
         broadcastDispatcher.unregisterReceiverForUser(broadcastReceiver, user0)
 
@@ -143,8 +172,8 @@
     @Test
     fun testRegisterCurrentAsActualUser() {
         setUserMock(mockContext, user1)
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler,
-                UserHandle.CURRENT)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, UserHandle.CURRENT)
 
         testableLooper.processAllMessages()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
index 21ed155..7821ae2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
@@ -26,6 +26,8 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
@@ -69,8 +71,6 @@
     @Mock
     private lateinit var mockContext: Context
     @Mock
-    private lateinit var mockHandler: Handler
-    @Mock
     private lateinit var mPendingResult: BroadcastReceiver.PendingResult
 
     @Captor
@@ -81,12 +81,14 @@
     private lateinit var intentFilter: IntentFilter
     private lateinit var intentFilterOther: IntentFilter
     private lateinit var handler: Handler
+    private lateinit var fakeExecutor: FakeExecutor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         handler = Handler(testableLooper.looper)
+        fakeExecutor = FakeExecutor(FakeSystemClock())
 
         userBroadcastDispatcher = UserBroadcastDispatcher(
                 mockContext, USER_ID, handler, testableLooper.looper)
@@ -108,7 +110,7 @@
         intentFilter = IntentFilter(ACTION_1)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         testableLooper.processAllMessages()
 
         assertTrue(userBroadcastDispatcher.isRegistered())
@@ -128,7 +130,7 @@
         intentFilter = IntentFilter(ACTION_1)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         testableLooper.processAllMessages()
         reset(mockContext)
 
@@ -151,9 +153,9 @@
         }
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, mockHandler, USER_HANDLE))
+                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         testableLooper.processAllMessages()
         assertTrue(userBroadcastDispatcher.isRegistered())
@@ -179,14 +181,15 @@
         intentFilterOther = IntentFilter(ACTION_2)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         val intent = Intent(ACTION_2)
 
         userBroadcastDispatcher.onReceive(mockContext, intent)
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         verify(broadcastReceiver, never()).onReceive(any(), any())
         verify(broadcastReceiverOther).onReceive(mockContext, intent)
@@ -198,14 +201,15 @@
         intentFilterOther = IntentFilter(ACTION_2)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilterOther, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         val intent = Intent(ACTION_2)
 
         userBroadcastDispatcher.onReceive(mockContext, intent)
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         verify(broadcastReceiver).onReceive(mockContext, intent)
     }
@@ -218,14 +222,15 @@
         intentFilterOther.addCategory(CATEGORY_2)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         val intent = Intent(ACTION_1)
 
         userBroadcastDispatcher.onReceive(mockContext, intent)
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         verify(broadcastReceiver).onReceive(mockContext, intent)
         verify(broadcastReceiverOther).onReceive(mockContext, intent)
@@ -235,12 +240,13 @@
     fun testPendingResult() {
         intentFilter = IntentFilter(ACTION_1)
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
 
         val intent = Intent(ACTION_1)
         userBroadcastDispatcher.onReceive(mockContext, intent)
 
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         verify(broadcastReceiver).onReceive(mockContext, intent)
         verify(broadcastReceiver).pendingResult = mPendingResult
@@ -250,15 +256,16 @@
     fun testRemoveReceiverReferences() {
         intentFilter = IntentFilter(ACTION_1)
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
 
         intentFilterOther = IntentFilter(ACTION_1)
         intentFilterOther.addAction(ACTION_2)
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         userBroadcastDispatcher.unregisterReceiver(broadcastReceiver)
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         assertFalse(userBroadcastDispatcher.isReceiverReferenceHeld(broadcastReceiver))
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 167f361..548da8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -110,7 +110,7 @@
     @Test
     public void testReceiverIsRegisteredToDispatcherOnStart() {
         mPowerUI.start();
-        verify(mBroadcastDispatcher).registerReceiver(
+        verify(mBroadcastDispatcher).registerReceiverWithHandler(
                 any(BroadcastReceiver.class),
                 any(IntentFilter.class),
                 any(Handler.class)); //PowerUI does not call with User
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 40b0ba9..3fdbd3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -47,6 +47,9 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
+import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache;
 import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
@@ -72,6 +75,7 @@
     public static final UserHandle USER_HANDLE = UserHandle.of(ActivityManager.getCurrentUser());
 
     private static final String GROUP_KEY = "gruKey";
+    private static final String APP_NAME = "appName";
 
     private final Context mContext;
     private int mId;
@@ -303,9 +307,6 @@
                 null /* root */,
                 false /* attachToRoot */);
         ExpandableNotificationRow row = mRow;
-        row.setGroupManager(mGroupManager);
-        row.setHeadsUpManager(mHeadsUpManager);
-        row.setAboveShelfChangedListener(aboveShelf -> {});
 
         final NotificationChannel channel =
                 new NotificationChannel(
@@ -329,6 +330,23 @@
         entry.setRow(row);
         entry.createIcons(mContext, entry.getSbn());
         row.setEntry(entry);
+
+        NotificationContentInflater contentBinder = new NotificationContentInflater(
+                mock(NotifRemoteViewCache.class),
+                mock(NotificationRemoteInputManager.class));
+        contentBinder.setInflateSynchronously(true);
+
+        row.initialize(
+                APP_NAME,
+                entry.getKey(),
+                mock(ExpansionLogger.class),
+                mock(KeyguardBypassController.class),
+                mGroupManager,
+                mHeadsUpManager,
+                contentBinder,
+                mock(OnExpandClickListener.class));
+        row.setAboveShelfChangedListener(aboveShelf -> { });
+
         row.setInflationFlags(extraInflationFlags);
         inflateAndWait(row);
 
@@ -341,11 +359,10 @@
 
     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,
+                    public void handleInflationException(NotificationEntry entry,
                             Exception e) {
                         countDownLatch.countDown();
                     }
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 f55ea4f..1e0179d 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
@@ -58,15 +58,13 @@
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dependency;
-import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
-import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -80,21 +78,24 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache;
+import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.leak.LeakDetector;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -128,21 +129,16 @@
     @Mock private HeadsUpManager mHeadsUpManager;
     @Mock private RankingMap mRankingMap;
     @Mock private RemoteInputController mRemoteInputController;
-
-    // Dependency mocks:
-    @Mock private ForegroundServiceController mForegroundServiceController;
+    @Mock private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
     @Mock private NotificationGroupManager mGroupManager;
     @Mock private NotificationGutsManager mGutsManager;
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
-    @Mock private NotificationListener mNotificationListener;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
-    @Mock private VisualStabilityManager mVisualStabilityManager;
-    @Mock private MetricsLogger mMetricsLogger;
-    @Mock private SmartReplyController mSmartReplyController;
     @Mock private RowInflaterTask mAsyncInflationTask;
-    @Mock private NotificationRowBinder mMockedRowBinder;
     @Mock private NotifLog mNotifLog;
+    @Mock private FeatureFlags mFeatureFlags;
+    @Mock private LeakDetector mLeakDetector;
 
     private int mId;
     private NotificationEntry mEntry;
@@ -189,21 +185,9 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mDependency.injectMockDependency(ShadeController.class);
-        mDependency.injectTestDependency(ForegroundServiceController.class,
-                mForegroundServiceController);
-        mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
-                mLockscreenUserManager);
-        mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
-        mDependency.injectTestDependency(NotificationGutsManager.class, mGutsManager);
-        mDependency.injectTestDependency(NotificationRemoteInputManager.class, mRemoteInputManager);
-        mDependency.injectTestDependency(NotificationListener.class, mNotificationListener);
-        mDependency.injectTestDependency(DeviceProvisionedController.class,
-                mDeviceProvisionedController);
-        mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
-        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
-        mDependency.injectTestDependency(SmartReplyController.class, mSmartReplyController);
-        mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
+        if (!mDependency.hasInstantiatedDependency(SmartReplyController.class)) {
+            mDependency.injectMockDependency(SmartReplyController.class);
+        }
         mDependency.injectMockDependency(NotificationMediaManager.class);
 
         mCountDownLatch = new CountDownLatch(1);
@@ -219,6 +203,25 @@
 
         mEntry.expandedIcon = mock(StatusBarIconView.class);
 
+        NotificationRowContentBinder contentBinder = new NotificationContentInflater(
+                mock(NotifRemoteViewCache.class),
+                mRemoteInputManager);
+
+        NotificationRowBinderImpl notificationRowBinder =
+                new NotificationRowBinderImpl(mContext,
+                        mRemoteInputManager,
+                        mLockscreenUserManager,
+                        contentBinder,
+                        true, /* allowLongPress */
+                        mock(KeyguardBypassController.class),
+                        mock(StatusBarStateController.class),
+                        mGroupManager,
+                        mGutsManager,
+                        mNotificationInterruptionStateProvider,
+                        mock(NotificationLogger.class));
+
+        when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false);
+        when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
         mEntryManager = new TestableNotificationEntryManager(
                 mNotifLog,
                 mGroupManager,
@@ -229,22 +232,22 @@
                         mock(NotificationFilter.class),
                         mNotifLog,
                         mock(NotificationSectionsFeatureManager.class),
-                        mock(PeopleNotificationIdentifier.class)),
-                mEnvironment
+                        mock(PeopleNotificationIdentifier.class),
+                        mock(HighPriorityProvider.class)),
+                mEnvironment,
+                mFeatureFlags,
+                () -> notificationRowBinder,
+                () -> mRemoteInputManager,
+                mLeakDetector
         );
         mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mHeadsUpManager);
         mEntryManager.addNotificationEntryListener(mEntryListener);
         mEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
 
-        NotificationRowBinderImpl notificationRowBinder =
-                new NotificationRowBinderImpl(mContext, true, /* allowLongPress */
-                        mock(KeyguardBypassController.class),
-                        mock(StatusBarStateController.class),
-                        mock(NotificationLogger.class));
         notificationRowBinder.setUpWithPresenter(
-                mPresenter, mListContainer, mHeadsUpManager, mEntryManager, mBindCallback);
+                mPresenter, mListContainer, mHeadsUpManager, mBindCallback);
+        notificationRowBinder.setInflationCallback(mEntryManager);
         notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class));
-        mEntryManager.setRowBinder(notificationRowBinder);
 
         setUserSentiment(
                 mEntry.getKey(), Ranking.USER_SENTIMENT_NEUTRAL);
@@ -364,9 +367,6 @@
 
     @Test
     public void testRemoveNotification_whilePending() {
-
-        mEntryManager.setRowBinder(mMockedRowBinder);
-
         mEntryManager.addNotification(mSbn, mRankingMap);
         mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
 
@@ -439,7 +439,6 @@
     @Test
     public void testLifetimeExtenders_ifNotificationIsRetainedItIsntRemoved() {
         // GIVEN an entry manager with a notification
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
 
         // GIVEN a lifetime extender that always tries to extend lifetime
@@ -463,7 +462,6 @@
         Assert.sMainLooper = TestableLooper.get(this).getLooper();
 
         // GIVEN an entry manager with a notification whose life has been extended
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
         final FakeNotificationLifetimeExtender extender = new FakeNotificationLifetimeExtender();
         mEntryManager.addNotificationLifetimeExtender(extender);
@@ -482,7 +480,6 @@
     @Test
     public void testLifetimeExtenders_whenNotificationUpdatedRetainersAreCanceled() {
         // GIVEN an entry manager with a notification whose life has been extended
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
         NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class);
         when(extender.shouldExtendLifetime(mEntry)).thenReturn(true);
@@ -499,7 +496,6 @@
     @Test
     public void testLifetimeExtenders_whenNewExtenderTakesPrecedenceOldExtenderIsCanceled() {
         // GIVEN an entry manager with a notification
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
 
         // GIVEN two lifetime extenders, the first which never extends and the second which
@@ -538,7 +534,6 @@
     @Test
     public void testRemoveInterceptor_interceptsDontGetRemoved() throws InterruptedException {
         // GIVEN an entry manager with a notification
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
 
         // GIVEN interceptor that intercepts that entry
@@ -560,7 +555,6 @@
         Assert.sMainLooper = TestableLooper.get(this).getLooper();
 
         // GIVEN an entry manager with a notification
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
 
         // GIVEN interceptor that doesn't intercept
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
index 34beefe..bf84f2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
@@ -16,13 +16,17 @@
 
 package com.android.systemui.statusbar.notification
 
+import com.android.systemui.statusbar.FeatureFlags
 import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinder
 import com.android.systemui.statusbar.notification.logging.NotifLog
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
 import com.android.systemui.statusbar.phone.NotificationGroupManager
+import com.android.systemui.util.leak.LeakDetector
 
 import java.util.concurrent.CountDownLatch
 
@@ -33,8 +37,13 @@
     log: NotifLog,
     gm: NotificationGroupManager,
     rm: NotificationRankingManager,
-    ke: KeyguardEnvironment
-) : NotificationEntryManager(log, gm, rm, ke) {
+    ke: KeyguardEnvironment,
+    ff: FeatureFlags,
+    rb: dagger.Lazy<NotificationRowBinder>,
+    notificationRemoteInputManagerLazy: dagger.Lazy<NotificationRemoteInputManager>,
+    leakDetector: LeakDetector
+) : NotificationEntryManager(log, gm, rm, ke, ff, rb,
+        notificationRemoteInputManagerLazy, leakDetector) {
 
     public var countDownLatch: CountDownLatch = CountDownLatch(1)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java
deleted file mode 100644
index a06d6c1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection;
-
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.RankingBuilder;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class GroupEntryTest extends SysuiTestCase {
-    @Test
-    public void testIsHighPriority_addChild() {
-        // GIVEN a GroupEntry with a lowPrioritySummary and no children
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        final NotificationEntry lowPrioritySummary = createNotifEntry(false);
-        setSummary(parentEntry, lowPrioritySummary);
-        assertFalse(parentEntry.isHighPriority());
-
-        // WHEN we add a high priority child and invalidate derived members
-        addChild(parentEntry, createNotifEntry(true));
-        parentEntry.invalidateDerivedMembers();
-
-        // THEN the GroupEntry's priority is updated to high even though the summary is still low
-        // priority
-        assertTrue(parentEntry.isHighPriority());
-        assertFalse(lowPrioritySummary.isHighPriority());
-    }
-
-    @Test
-    public void testIsHighPriority_clearChildren() {
-        // GIVEN a GroupEntry with a lowPrioritySummary and high priority children
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        setSummary(parentEntry, createNotifEntry(false));
-        addChild(parentEntry, createNotifEntry(true));
-        addChild(parentEntry, createNotifEntry(true));
-        addChild(parentEntry, createNotifEntry(true));
-        assertTrue(parentEntry.isHighPriority());
-
-        // WHEN we clear the children and invalidate derived members
-        parentEntry.clearChildren();
-        parentEntry.invalidateDerivedMembers();
-
-        // THEN the parentEntry isn't high priority anymore
-        assertFalse(parentEntry.isHighPriority());
-    }
-
-    @Test
-    public void testIsHighPriority_summaryUpdated() {
-        // GIVEN a GroupEntry with a lowPrioritySummary and no children
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        final NotificationEntry lowPrioritySummary = createNotifEntry(false);
-        setSummary(parentEntry, lowPrioritySummary);
-        assertFalse(parentEntry.isHighPriority());
-
-        // WHEN the summary changes to high priority and invalidates its derived members
-        lowPrioritySummary.setRanking(
-                new RankingBuilder()
-                        .setKey(lowPrioritySummary.getKey())
-                        .setImportance(IMPORTANCE_HIGH)
-                        .build());
-        lowPrioritySummary.invalidateDerivedMembers();
-        assertTrue(lowPrioritySummary.isHighPriority());
-
-        // THEN the GroupEntry's priority is updated to high
-        assertTrue(parentEntry.isHighPriority());
-    }
-
-    @Test
-    public void testIsHighPriority_checkChildrenToCalculatePriority() {
-        // GIVEN:
-        // GroupEntry = parentEntry, summary = lowPrioritySummary
-        //      NotificationEntry = lowPriorityChild
-        //      NotificationEntry = highPriorityChild
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        setSummary(parentEntry, createNotifEntry(false));
-        addChild(parentEntry, createNotifEntry(false));
-        addChild(parentEntry, createNotifEntry(true));
-
-        // THEN the GroupEntry parentEntry is high priority since it has a high priority child
-        assertTrue(parentEntry.isHighPriority());
-    }
-
-    @Test
-    public void testIsHighPriority_childEntryRankingUpdated() {
-        // GIVEN:
-        // GroupEntry = parentEntry, summary = lowPrioritySummary
-        //      NotificationEntry = lowPriorityChild
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        final NotificationEntry lowPriorityChild = createNotifEntry(false);
-        setSummary(parentEntry, createNotifEntry(false));
-        addChild(parentEntry, lowPriorityChild);
-
-        // WHEN the child entry ranking changes to high priority and invalidates its derived members
-        lowPriorityChild.setRanking(
-                new RankingBuilder()
-                        .setKey(lowPriorityChild.getKey())
-                        .setImportance(IMPORTANCE_HIGH)
-                        .build());
-        lowPriorityChild.invalidateDerivedMembers();
-
-        // THEN the parent entry's high priority value is updated - but not the parent's summary
-        assertTrue(parentEntry.isHighPriority());
-        assertFalse(parentEntry.getSummary().isHighPriority());
-    }
-
-    private NotificationEntry createNotifEntry(boolean highPriority) {
-        return new NotificationEntryBuilder()
-                .setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN)
-                .build();
-    }
-
-    private void setSummary(GroupEntry parent, NotificationEntry summary) {
-        parent.setSummary(summary);
-        summary.setParent(parent);
-    }
-
-    private void addChild(GroupEntry parent, NotificationEntry child) {
-        parent.addChild(child);
-        child.setParent(parent);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
new file mode 100644
index 0000000..93909dc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class HighPriorityProviderTest extends SysuiTestCase {
+    @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+    private HighPriorityProvider mHighPriorityProvider;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mHighPriorityProvider = new HighPriorityProvider(mPeopleNotificationIdentifier);
+    }
+
+    @Test
+    public void highImportance() {
+        // GIVEN notification has high importance
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setImportance(IMPORTANCE_HIGH)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false);
+
+        // THEN it has high priority
+        assertTrue(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void peopleNotification() {
+        // GIVEN notification is low importance and is a people notification
+        final Notification notification = new Notification.Builder(mContext, "test")
+                .build();
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setImportance(IMPORTANCE_LOW)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(true);
+
+        // THEN it has high priority
+        assertTrue(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void messagingStyle() {
+        // GIVEN notification is low importance but has messaging style
+        final Notification notification = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle(""))
+                .build();
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false);
+
+        // THEN it has high priority
+        assertTrue(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void lowImportanceForeground() {
+        // GIVEN notification is low importance and is associated with a foreground service
+        final Notification notification = mock(Notification.class);
+        when(notification.isForegroundService()).thenReturn(true);
+
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setImportance(IMPORTANCE_LOW)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false);
+
+        // THEN it has high priority
+        assertTrue(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void minImportanceForeground() {
+        // GIVEN notification is low importance and is associated with a foreground service
+        final Notification notification = mock(Notification.class);
+        when(notification.isForegroundService()).thenReturn(true);
+
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setImportance(IMPORTANCE_MIN)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false);
+
+        // THEN it does NOT have high priority
+        assertFalse(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void userChangeTrumpsHighPriorityCharacteristics() {
+        // GIVEN notification has high priority characteristics but the user changed the importance
+        // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN)
+        final Notification notification = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle(""))
+                .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                .build();
+        final NotificationChannel channel = new NotificationChannel("a", "a",
+                IMPORTANCE_LOW);
+        channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setChannel(channel)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(true);
+
+        // THEN it does NOT have high priority
+        assertFalse(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void testIsHighPriority_summaryUpdated() {
+        // GIVEN a GroupEntry with a lowPrioritySummary and no children
+        final GroupEntry parentEntry = new GroupEntry("test_group_key");
+        final NotificationEntry lowPrioritySummary = createNotifEntry(false);
+        setSummary(parentEntry, lowPrioritySummary);
+        assertFalse(mHighPriorityProvider.isHighPriority(parentEntry));
+
+        // WHEN the summary changes to high priority
+        lowPrioritySummary.setRanking(
+                new RankingBuilder()
+                        .setKey(lowPrioritySummary.getKey())
+                        .setImportance(IMPORTANCE_HIGH)
+                        .build());
+        assertTrue(mHighPriorityProvider.isHighPriority(lowPrioritySummary));
+
+        // THEN the GroupEntry's priority is updated to high
+        assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
+    }
+
+    @Test
+    public void testIsHighPriority_checkChildrenToCalculatePriority() {
+        // GIVEN:
+        // GroupEntry = parentEntry, summary = lowPrioritySummary
+        //      NotificationEntry = lowPriorityChild
+        //      NotificationEntry = highPriorityChild
+        final GroupEntry parentEntry = new GroupEntry("test_group_key");
+        setSummary(parentEntry, createNotifEntry(false));
+        addChild(parentEntry, createNotifEntry(false));
+        addChild(parentEntry, createNotifEntry(true));
+
+        // THEN the GroupEntry parentEntry is high priority since it has a high priority child
+        assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
+    }
+
+    @Test
+    public void testIsHighPriority_childEntryRankingUpdated() {
+        // GIVEN:
+        // GroupEntry = parentEntry, summary = lowPrioritySummary
+        //      NotificationEntry = lowPriorityChild
+        final GroupEntry parentEntry = new GroupEntry("test_group_key");
+        final NotificationEntry lowPriorityChild = createNotifEntry(false);
+        setSummary(parentEntry, createNotifEntry(false));
+        addChild(parentEntry, lowPriorityChild);
+
+        // WHEN the child entry ranking changes to high priority
+        lowPriorityChild.setRanking(
+                new RankingBuilder()
+                        .setKey(lowPriorityChild.getKey())
+                        .setImportance(IMPORTANCE_HIGH)
+                        .build());
+
+        // THEN the parent entry's high priority value is updated - but not the parent's summary
+        assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
+        assertFalse(mHighPriorityProvider.isHighPriority(parentEntry.getSummary()));
+    }
+
+    private NotificationEntry createNotifEntry(boolean highPriority) {
+        return new NotificationEntryBuilder()
+                .setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN)
+                .build();
+    }
+
+    private void setSummary(GroupEntry parent, NotificationEntry summary) {
+        parent.setSummary(summary);
+        summary.setParent(parent);
+    }
+
+    private void addChild(GroupEntry parent, NotificationEntry child) {
+        parent.addChild(child);
+        child.setParent(parent);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 0837a42..28feaca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -261,9 +262,13 @@
                 .setRank(5)
                 .setExplanation("baz buzz")
                 .build();
+
+        // WHEN entry3's ranking update includes an update to its overrideGroupKey
+        final String newOverrideGroupKey = "newOverrideGroupKey";
         Ranking newRanking3 = new RankingBuilder(notif3.ranking)
                 .setRank(6)
                 .setExplanation("Penguin pizza")
+                .setOverrideGroupKey(newOverrideGroupKey)
                 .build();
 
         mNoMan.setRanking(notif1.sbn.getKey(), newRanking1);
@@ -275,6 +280,10 @@
         assertEquals(newRanking1, entry1.getRanking());
         assertEquals(newRanking2, entry2.getRanking());
         assertEquals(newRanking3, entry3.getRanking());
+
+        // THEN the entry3's overrideGroupKey is updated along with its groupKey
+        assertEquals(newOverrideGroupKey, entry3.getSbn().getOverrideGroupKey());
+        assertNotNull(entry3.getSbn().getGroupKey());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 39ae68a..5b0b668 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -21,8 +21,6 @@
 import static android.app.Notification.CATEGORY_EVENT;
 import static android.app.Notification.CATEGORY_MESSAGE;
 import static android.app.Notification.CATEGORY_REMINDER;
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
 
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
@@ -92,44 +90,6 @@
     }
 
     @Test
-    public void testIsHighPriority_notificationUpdates() {
-        // GIVEN a notification with high importance
-        final NotificationEntry entryHigh = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-
-        // WHEN we get the value for the high priority entry, we're caching the high priority value
-        assertTrue(entryHigh.isHighPriority());
-
-        // WHEN we change the ranking and derived members (high priority) are invalidated
-        entryHigh.setRanking(
-                new RankingBuilder()
-                        .setKey(entryHigh.getKey())
-                        .setImportance(IMPORTANCE_MIN)
-                        .build());
-        entryHigh.invalidateDerivedMembers();
-
-        // THEN the priority is recalculated and is now low
-        assertFalse(entryHigh.isHighPriority());
-
-        // WHEN the sbn is updated to have messaging style (high priority characteristic)
-        //  AND the entry invalidates its derived members
-        final Notification notification =
-                new Notification.Builder(mContext, "test")
-                        .setStyle(new Notification.MessagingStyle(""))
-                        .build();
-        final StatusBarNotification sbn = entryHigh.getSbn();
-        entryHigh.setSbn(new StatusBarNotification(
-                sbn.getPackageName(), sbn.getPackageName(),
-                sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
-                notification, sbn.getUser(), sbn.getOverrideGroupKey(), 0));
-        entryHigh.invalidateDerivedMembers();
-
-        // THEN the priority is recalculated and is now high
-        assertTrue(entryHigh.isHighPriority());
-    }
-
-    @Test
     public void testIsExemptFromDndVisualSuppression_foreground() {
         mEntry.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
index 10450fa..e273191 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.notification.NotificationFilter
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
 import com.android.systemui.statusbar.notification.logging.NotifLog
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
@@ -62,7 +63,8 @@
                 mock(NotificationFilter::class.java),
                 mock(NotifLog::class.java),
                 mock(NotificationSectionsFeatureManager::class.java),
-                personNotificationIdentifier
+                personNotificationIdentifier,
+                HighPriorityProvider(personNotificationIdentifier)
         )
     }
 
@@ -182,7 +184,8 @@
         filter: NotificationFilter,
         notifLog: NotifLog,
         sectionsFeatureManager: NotificationSectionsFeatureManager,
-        peopleNotificationIdentifier: PeopleNotificationIdentifier
+        peopleNotificationIdentifier: PeopleNotificationIdentifier,
+        highPriorityProvider: HighPriorityProvider
     ) : NotificationRankingManager(
         mediaManager,
         groupManager,
@@ -190,7 +193,8 @@
         filter,
         notifLog,
         sectionsFeatureManager,
-        peopleNotificationIdentifier
+        peopleNotificationIdentifier,
+        highPriorityProvider
     ) {
         fun applyTestRankingMap(r: RankingMap) {
             rankingMap = r
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
index 979b8a9..f921cf9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
@@ -66,6 +67,7 @@
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock private HighPriorityProvider mHighPriorityProvider;
     @Mock private NotifListBuilderImpl mNotifListBuilder;
 
     private NotificationEntry mEntry;
@@ -78,7 +80,7 @@
         mKeyguardCoordinator = new KeyguardCoordinator(
                 mContext, mMainHandler, mKeyguardStateController, mLockscreenUserManager,
                 mBroadcastDispatcher, mStatusBarStateController,
-                mKeyguardUpdateMonitor);
+                mKeyguardUpdateMonitor, mHighPriorityProvider);
 
         mEntry = new NotificationEntryBuilder()
                 .setUser(new UserHandle(NOTIF_USER_ID))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java
deleted file mode 100644
index 6fa1a89..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.provider;
-
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_LOW;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.Person;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class IsHighPriorityProviderTest extends SysuiTestCase {
-    private IsHighPriorityProvider mIsHighPriorityProvider;
-
-    @Before
-    public void setup() {
-        mIsHighPriorityProvider = new IsHighPriorityProvider();
-    }
-
-    @Test
-    public void testCache() {
-        // GIVEN a notification with high importance
-        final NotificationEntry entryHigh = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-
-        // GIVEN notification with min importance
-        final NotificationEntry entryMin = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_MIN)
-                .build();
-
-        // WHEN we get the value for the high priority entry
-        assertTrue(mIsHighPriorityProvider.get(entryHigh));
-
-        // THEN the value is cached, so even when passed an entryMin, we still get high priority
-        assertTrue(mIsHighPriorityProvider.get(entryMin));
-
-        // UNTIL the provider is invalidated
-        mIsHighPriorityProvider.invalidate();
-
-        // THEN the priority is recalculated
-        assertFalse(mIsHighPriorityProvider.get(entryMin));
-    }
-
-    @Test
-    public void highImportance() {
-        // GIVEN notification has high importance
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-
-        // THEN it has high priority
-        assertTrue(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void peopleNotification() {
-        // GIVEN notification is low importance but has a person associated with it
-        final Notification notification = new Notification.Builder(mContext, "test")
-                .addPerson(
-                        new Person.Builder()
-                                .setName("name")
-                                .setKey("abc")
-                                .setUri("uri")
-                                .setBot(true)
-                                .build())
-                .build();
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .setImportance(IMPORTANCE_LOW)
-                .build();
-
-        // THEN it has high priority
-        assertTrue(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void messagingStyle() {
-        // GIVEN notification is low importance but has messaging style
-        final Notification notification = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle(""))
-                .build();
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .build();
-
-        // THEN it has high priority
-        assertTrue(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void lowImportanceForeground() {
-        // GIVEN notification is low importance and is associated with a foreground service
-        final Notification notification = mock(Notification.class);
-        when(notification.isForegroundService()).thenReturn(true);
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .setImportance(IMPORTANCE_LOW)
-                .build();
-
-        // THEN it has high priority
-        assertTrue(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void minImportanceForeground() {
-        // GIVEN notification is low importance and is associated with a foreground service
-        final Notification notification = mock(Notification.class);
-        when(notification.isForegroundService()).thenReturn(true);
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .setImportance(IMPORTANCE_MIN)
-                .build();
-
-        // THEN it does NOT have high priority
-        assertFalse(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void userChangeTrumpsHighPriorityCharacteristics() {
-        // GIVEN notification has high priority characteristics but the user changed the importance
-        // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN)
-        final Notification notification = new Notification.Builder(mContext, "test")
-                .addPerson(
-                        new Person.Builder()
-                                .setName("name")
-                                .setKey("abc")
-                                .setUri("uri")
-                                .setBot(true)
-                                .build())
-                .setStyle(new Notification.MessagingStyle(""))
-                .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
-                .build();
-
-        final NotificationChannel channel = new NotificationChannel("a", "a",
-                IMPORTANCE_LOW);
-        channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .setChannel(channel)
-                .build();
-
-        // THEN it does NOT have high priority
-        assertFalse(mIsHighPriorityProvider.get(entry));
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
index 0764d0c..867a9b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
@@ -178,9 +178,10 @@
 private fun fakePersonModel(
     id: String,
     name: CharSequence,
-    clickIntent: PendingIntent
+    clickIntent: PendingIntent,
+    userId: Int = 0
 ): PersonModel =
-        PersonModel(id, name, mock(Drawable::class.java), clickIntent)
+        PersonModel(id, name, mock(Drawable::class.java), clickIntent, userId)
 
 private fun fakePersonViewModel(name: CharSequence): PersonViewModel =
         PersonViewModel(name, mock(Drawable::class.java), mock({}.javaClass))
@@ -207,4 +208,4 @@
     override fun onDataChanged(data: T) {
         lastSeen = Maybe.Just(data)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
new file mode 100644
index 0000000..d7214f3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.widget.RemoteViews;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class NotifRemoteViewCacheImplTest extends SysuiTestCase {
+
+    private NotifRemoteViewCacheImpl mNotifRemoteViewCache;
+    private NotificationEntry mEntry;
+    private NotificationEntryListener mEntryListener;
+    @Mock private RemoteViews mRemoteViews;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mEntry = new NotificationEntryBuilder().build();
+
+        NotificationEntryManager entryManager = mock(NotificationEntryManager.class);
+        mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(entryManager);
+        ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
+                ArgumentCaptor.forClass(NotificationEntryListener.class);
+        verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture());
+        mEntryListener = entryListenerCaptor.getValue();
+    }
+
+    @Test
+    public void testPutCachedView() {
+        // GIVEN an initialized cache for an entry.
+        mEntryListener.onPendingEntryAdded(mEntry);
+
+        // WHEN a notification's cached remote views is put in.
+        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+
+        // THEN the remote view is cached.
+        assertTrue(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+        assertEquals(
+                "Cached remote view is not the one we put in.",
+                mRemoteViews,
+                mNotifRemoteViewCache.getCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+    }
+
+    @Test
+    public void testRemoveCachedView() {
+        // GIVEN a cache with a cached view.
+        mEntryListener.onPendingEntryAdded(mEntry);
+        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+
+        // WHEN we remove the cached view.
+        mNotifRemoteViewCache.removeCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED);
+
+        // THEN the remote view is not cached.
+        assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+    }
+
+    @Test
+    public void testClearCache() {
+        // GIVEN a non-empty cache.
+        mEntryListener.onPendingEntryAdded(mEntry);
+        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED, mRemoteViews);
+
+        // WHEN we clear the cache.
+        mNotifRemoteViewCache.clearCache(mEntry);
+
+        // THEN the cache is empty.
+        assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+        assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index dc4e498..cb9da6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -22,30 +22,35 @@
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.Notification;
 import android.content.Context;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Looper;
-import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
-import android.util.ArrayMap;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.RemoteViews;
+import android.widget.TextView;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.filters.Suppress;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.InflationTask;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
@@ -58,6 +63,8 @@
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.HashMap;
 import java.util.concurrent.CountDownLatch;
@@ -74,8 +81,11 @@
     private Notification.Builder mBuilder;
     private ExpandableNotificationRow mRow;
 
+    @Mock private NotifRemoteViewCache mCache;
+
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
         mBuilder = new Notification.Builder(mContext).setSmallIcon(
                 R.drawable.ic_person)
                 .setContentTitle("Title")
@@ -84,7 +94,9 @@
         ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
                 mBuilder.build());
         mRow = spy(row);
-        mNotificationInflater = new NotificationContentInflater();
+        mNotificationInflater = new NotificationContentInflater(
+                mCache,
+                mock(NotificationRemoteInputManager.class));
     }
 
     @Test
@@ -175,11 +187,13 @@
                 result,
                 FLAG_CONTENT_VIEW_EXPANDED,
                 0,
-                new ArrayMap() /* cachedContentViews */, mRow,
+                mock(NotifRemoteViewCache.class),
+                mRow.getEntry(),
+                mRow,
                 true /* isNewView */, (v, p, r) -> true,
                 new InflationCallback() {
                     @Override
-                    public void handleInflationException(StatusBarNotification notification,
+                    public void handleInflationException(NotificationEntry entry,
                             Exception e) {
                         countDownLatch.countDown();
                         throw new RuntimeException("No Exception expected");
@@ -245,6 +259,71 @@
                 NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView));
     }
 
+    @Test
+    public void testUsesSameViewWhenCachedPossibleToReuse() throws Exception {
+        // GIVEN a cached view.
+        RemoteViews contractedRemoteView = mBuilder.createContentView();
+        when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+                .thenReturn(true);
+        when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+                .thenReturn(contractedRemoteView);
+
+        // GIVEN existing bound view with same layout id.
+        View view = contractedRemoteView.apply(mContext, null /* parent */);
+        mRow.getPrivateLayout().setContractedChild(view);
+
+        // WHEN inflater inflates
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+        // THEN the view should be re-used
+        assertEquals("Binder inflated a new view even though the old one was cached and usable.",
+                view, mRow.getPrivateLayout().getContractedChild());
+    }
+
+    @Test
+    public void testInflatesNewViewWhenCachedNotPossibleToReuse() throws Exception {
+        // GIVEN a cached remote view.
+        RemoteViews contractedRemoteView = mBuilder.createHeadsUpContentView();
+        when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+                .thenReturn(true);
+        when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+                .thenReturn(contractedRemoteView);
+
+        // GIVEN existing bound view with different layout id.
+        View view = new TextView(mContext);
+        mRow.getPrivateLayout().setContractedChild(view);
+
+        // WHEN inflater inflates
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+        // THEN the view should be a new view
+        assertNotEquals("Binder (somehow) used the same view when inflating.",
+                view, mRow.getPrivateLayout().getContractedChild());
+    }
+
+    @Test
+    public void testInflationCachesCreatedRemoteView() throws Exception {
+        // WHEN inflater inflates
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+        // THEN inflater informs cache of the new remote view
+        verify(mCache).putCachedView(
+                eq(mRow.getEntry()),
+                eq(FLAG_CONTENT_VIEW_CONTRACTED),
+                any());
+    }
+
+    @Test
+    public void testUnbindRemovesCachedRemoteView() {
+        // WHEN inflated unbinds content
+        mNotificationInflater.unbindContent(mRow.getEntry(), mRow, FLAG_CONTENT_VIEW_HEADS_UP);
+
+        // THEN inflated informs cache to remove remote view
+        verify(mCache).removeCachedView(
+                eq(mRow.getEntry()),
+                eq(FLAG_CONTENT_VIEW_HEADS_UP));
+    }
+
     private static void inflateAndWait(NotificationContentInflater inflater,
             @InflationFlag int contentToInflate,
             ExpandableNotificationRow row)
@@ -261,7 +340,7 @@
         inflater.setInflateSynchronously(true);
         InflationCallback callback = new InflationCallback() {
             @Override
-            public void handleInflationException(StatusBarNotification notification,
+            public void handleInflationException(NotificationEntry entry,
                     Exception e) {
                 if (!expectingException) {
                     exceptionHolder.setException(e);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index 675b3ef..84c6513 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -16,20 +16,17 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
-import android.graphics.drawable.Icon;
 import android.util.ArraySet;
 import android.view.NotificationHeaderView;
 import android.view.View;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index ccc9496..4e27770 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -70,6 +70,7 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -113,6 +114,7 @@
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
     @Mock private StatusBar mStatusBar;
     @Mock private AccessibilityManager mAccessibilityManager;
+    @Mock private HighPriorityProvider mHighPriorityProvider;
 
     @Before
     public void setUp() {
@@ -128,7 +130,7 @@
         when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
 
         mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager,
-                () -> mStatusBar, mHandler, mAccessibilityManager);
+                () -> mStatusBar, mHandler, mAccessibilityManager, mHighPriorityProvider);
         mGutsManager.setUpWithPresenter(mPresenter, mStackScroller,
                 mCheckSaveListener, mOnSettingsClickListener);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
@@ -391,6 +393,7 @@
                 .build();
 
         when(row.getIsNonblockable()).thenReturn(false);
+        when(mHighPriorityProvider.isHighPriority(entry)).thenReturn(true);
         StatusBarNotification statusBarNotification = entry.getSbn();
         mGutsManager.initializeNotificationInfo(row, notificationInfoView);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 003d803..518b670 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -267,8 +267,6 @@
                     ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class,
                             RETURNS_DEEP_STUBS);
                     when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
-                    when(notifRow.getEntry().isHighPriority())
-                            .thenReturn(children[i] == ChildType.HIPRI);
                     when(notifRow.getEntry().getBucket()).thenReturn(
                             children[i] == ChildType.HIPRI ? BUCKET_ALERTING : BUCKET_SILENT);
                     when(notifRow.getParent()).thenReturn(mNssl);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 77a6a26..9bd3914 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
@@ -53,6 +53,7 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -70,6 +71,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -87,6 +90,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.leak.LeakDetector;
 
 import org.junit.After;
 import org.junit.Before;
@@ -164,9 +168,14 @@
                         mock(NotificationFilter.class),
                         mock(NotifLog.class),
                         mock(NotificationSectionsFeatureManager.class),
-                        mock(PeopleNotificationIdentifier.class)
+                        mock(PeopleNotificationIdentifier.class),
+                        mock(HighPriorityProvider.class)
                 ),
-                mock(NotificationEntryManager.KeyguardEnvironment.class));
+                mock(NotificationEntryManager.KeyguardEnvironment.class),
+                mock(FeatureFlags.class),
+                () -> mock(NotificationRowBinder.class),
+                () -> mRemoteInputManager,
+                mock(LeakDetector.class));
         mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
         mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index 39afbe0..8f645b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -190,7 +190,7 @@
         mFragments.dispatchResume();
         processAllMessages();
 
-        verify(mBroadcastDispatcher).registerReceiver(
+        verify(mBroadcastDispatcher).registerReceiverWithHandler(
                 any(BroadcastReceiver.class),
                 any(IntentFilter.class),
                 any(Handler.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 7e485f4..560aadb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -120,6 +120,7 @@
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -255,6 +256,7 @@
     @Mock private KeyguardDismissUtil mKeyguardDismissUtil;
     @Mock private ExtensionController mExtensionController;
     @Mock private UserInfoControllerImpl mUserInfoControllerImpl;
+    @Mock private NotificationRowBinderImpl mNotificationRowBinder;
     private ShadeController mShadeController;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
     private InitController mInitController = new InitController();
@@ -344,7 +346,6 @@
                 mHeadsUpManager,
                 mDynamicPrivacyController,
                 mBypassHeadsUpNotifier,
-                true,
                 () -> mNewNotifPipeline,
                 new FalsingManagerFake(),
                 mBroadcastDispatcher,
@@ -413,6 +414,7 @@
                 mKeyguardDismissUtil,
                 mExtensionController,
                 mUserInfoControllerImpl,
+                mNotificationRowBinder,
                 mDismissCallbackRegistry);
 
         when(mStatusBarWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index c4caeb3..9cb06a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -180,7 +180,7 @@
     protected void setupNetworkController() {
         // For now just pretend to be the data sim, so we can test that too.
         mSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
-        when(mMockTm.isDataCapable()).thenReturn(true);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(true);
         setDefaultSubId(mSubId);
         setSubscriptions(mSubId);
         mMobileSignalController = mNetworkController.mMobileSignalControllers.get(mSubId);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 95b055c..5a5ef8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -121,7 +121,7 @@
     @Test
     public void testNoInternetIcon_withDefaultSub() {
         setupNetworkController();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         setupDefaultSignal();
         updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0);
         setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -135,7 +135,7 @@
     @Test
     public void testDataDisabledIcon_withDefaultSub() {
         setupNetworkController();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         setupDefaultSignal();
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
         setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -149,7 +149,7 @@
     @Test
     public void testNonDefaultSIM_showsFullSignal_connected() {
         setupNetworkController();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         setupDefaultSignal();
         setDefaultSubId(mSubId + 1);
         updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0);
@@ -164,7 +164,7 @@
     @Test
     public void testNonDefaultSIM_showsFullSignal_disconnected() {
         setupNetworkController();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         setupDefaultSignal();
         setDefaultSubId(mSubId + 1);
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
@@ -429,7 +429,7 @@
     @Test
     public void testDataDisabledIcon_UserNotSetup() {
         setupNetworkController();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         setupDefaultSignal();
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
         setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -444,7 +444,7 @@
     @Test
     public void testAlwaysShowDataRatIcon() {
         setupDefaultSignal();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED,
                 TelephonyManager.NETWORK_TYPE_GSM);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 2854665..80aa6f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -69,7 +69,7 @@
 
     @Test
     public void testRegisteredWithDispatcher() {
-        verify(mBroadcastDispatcher).registerReceiver(any(BroadcastReceiver.class),
+        verify(mBroadcastDispatcher).registerReceiverWithHandler(any(BroadcastReceiver.class),
                 any(IntentFilter.class),
                 any(Handler.class)); // VolumeDialogControllerImpl does not call with user
     }
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index 0be853a..d297f3f 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -20,7 +20,7 @@
     srcs: [
         "src/**/*.java",
         ":framework-tethering-shared-srcs",
-        ":net-module-utils-srcs",
+        ":tethering-module-utils-srcs",
         ":services-tethering-shared-srcs",
     ],
     static_libs: [
@@ -123,4 +123,5 @@
     use_embedded_native_libs: true,
     // The permission configuration *must* be included to ensure security of the device
     required: ["NetworkPermissionConfig"],
+    apex_available: ["com.android.tethering"],
 }
diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml
index 87a8c3f..e99c2c5 100644
--- a/packages/Tethering/AndroidManifest.xml
+++ b/packages/Tethering/AndroidManifest.xml
@@ -33,6 +33,7 @@
     <uses-permission android:name="android.permission.MANAGE_USB" />
     <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
     <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
+    <uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
     <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
 
diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp
index 5785707..264ce44 100644
--- a/packages/Tethering/common/TetheringLib/Android.bp
+++ b/packages/Tethering/common/TetheringLib/Android.bp
@@ -47,6 +47,16 @@
     libs: [
         "android_system_stubs_current",
     ],
+
+    hostdex: true, // for hiddenapi check
+    visibility: [
+        "//frameworks/base/packages/Tethering:__subpackages__",
+        //TODO(b/147200698) remove below lines when the platform is built with stubs
+        "//frameworks/base",
+        "//frameworks/base/services",
+        "//frameworks/base/services/core",
+    ],
+    apex_available: ["com.android.tethering"],
 }
 
 filegroup {
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index a49ab85..11e5718 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -15,8 +15,6 @@
  */
 package android.net;
 
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-
 import android.annotation.NonNull;
 import android.content.Context;
 import android.net.ConnectivityManager.OnTetheringEventCallback;
@@ -52,6 +50,103 @@
     private TetheringConfigurationParcel mTetheringConfiguration;
     private TetherStatesParcel mTetherStatesParcel;
 
+    /**
+     * Broadcast Action: A tetherable connection has come or gone.
+     * Uses {@code TetheringManager.EXTRA_AVAILABLE_TETHER},
+     * {@code TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY},
+     * {@code TetheringManager.EXTRA_ACTIVE_TETHER}, and
+     * {@code TetheringManager.EXTRA_ERRORED_TETHER} to indicate
+     * the current state of tethering.  Each include a list of
+     * interface names in that state (may be empty).
+     */
+    public static final String ACTION_TETHER_STATE_CHANGED =
+            "android.net.conn.TETHER_STATE_CHANGED";
+
+    /**
+     * gives a String[] listing all the interfaces configured for
+     * tethering and currently available for tethering.
+     */
+    public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
+
+    /**
+     * gives a String[] listing all the interfaces currently in local-only
+     * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding)
+     */
+    public static final String EXTRA_ACTIVE_LOCAL_ONLY = "localOnlyArray";
+
+    /**
+     * gives a String[] listing all the interfaces currently tethered
+     * (ie, has DHCPv4 support and packets potentially forwarded/NATed)
+     */
+    public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
+
+    /**
+     * gives a String[] listing all the interfaces we tried to tether and
+     * failed.  Use {@link #getLastTetherError} to find the error code
+     * for any interfaces listed here.
+     */
+    public static final String EXTRA_ERRORED_TETHER = "erroredArray";
+
+    /**
+     * Invalid tethering type.
+     * @see #startTethering.
+     */
+    public static final int TETHERING_INVALID   = -1;
+
+    /**
+     * Wifi tethering type.
+     * @see #startTethering.
+     */
+    public static final int TETHERING_WIFI      = 0;
+
+    /**
+     * USB tethering type.
+     * @see #startTethering.
+     */
+    public static final int TETHERING_USB       = 1;
+
+    /**
+     * Bluetooth tethering type.
+     * @see #startTethering.
+     */
+    public static final int TETHERING_BLUETOOTH = 2;
+
+    /**
+     * Wifi P2p tethering type.
+     * Wifi P2p tethering is set through events automatically, and don't
+     * need to start from #startTethering.
+     */
+    public static final int TETHERING_WIFI_P2P = 3;
+
+    /**
+     * Extra used for communicating with the TetherService. Includes the type of tethering to
+     * enable if any.
+     */
+    public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
+
+    /**
+     * Extra used for communicating with the TetherService. Includes the type of tethering for
+     * which to cancel provisioning.
+     */
+    public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
+
+    /**
+     * Extra used for communicating with the TetherService. True to schedule a recheck of tether
+     * provisioning.
+     */
+    public static final String EXTRA_SET_ALARM = "extraSetAlarm";
+
+    /**
+     * Tells the TetherService to run a provision check now.
+     */
+    public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
+
+    /**
+     * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver}
+     * which will receive provisioning results. Can be left empty.
+     */
+    public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
+
     public static final int TETHER_ERROR_NO_ERROR           = 0;
     public static final int TETHER_ERROR_UNKNOWN_IFACE      = 1;
     public static final int TETHER_ERROR_SERVICE_UNAVAIL    = 2;
@@ -470,7 +565,7 @@
      * failed.  Re-attempting to tether may cause them to reset to the Tethered
      * state.  Alternatively, causing the interface to be destroyed and recreated
      * may cause them to reset to the available state.
-     * {@link ConnectivityManager#getLastTetherError} can be used to get more
+     * {@link TetheringManager#getLastTetherError} can be used to get more
      * information on the cause of the errors.
      *
      * @return an array of 0 or more String indicating the interface names
diff --git a/packages/Tethering/jarjar-rules.txt b/packages/Tethering/jarjar-rules.txt
index d93531b..c6efa41 100644
--- a/packages/Tethering/jarjar-rules.txt
+++ b/packages/Tethering/jarjar-rules.txt
@@ -11,6 +11,7 @@
 rule com.android.internal.util.Preconditions* com.android.networkstack.tethering.util.Preconditions@1
 rule com.android.internal.util.State* com.android.networkstack.tethering.util.State@1
 rule com.android.internal.util.StateMachine* com.android.networkstack.tethering.util.StateMachine@1
+rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.tethering.util.TrafficStatsConstants@1
 
 rule android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1
 
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml
index 37e679d..c29e678 100644
--- a/packages/Tethering/res/values/config.xml
+++ b/packages/Tethering/res/values/config.xml
@@ -1,7 +1,149 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
 <resources>
     <!--
     OEMs that wish to change the below settings must do so via a runtime resource overlay package
     and *NOT* by changing this file. This file is part of the tethering mainline module.
+    TODO: define two resources for each config item: a default_* resource and a config_* resource,
+    config_* is empty by default but may be overridden by RROs.
     -->
+    <!-- List of regexpressions describing the interface (if any) that represent tetherable
+         USB interfaces.  If the device doesn't want to support tethering over USB this should
+         be empty.  An example would be "usb.*" -->
+    <string-array translatable="false" name="config_tether_usb_regexs">
+        <item>"usb\\d"</item>
+        <item>"rndis\\d"</item>
+    </string-array>
+
+    <!-- List of regexpressions describing the interface (if any) that represent tetherable
+         Wifi interfaces.  If the device doesn't want to support tethering over Wifi this
+         should be empty.  An example would be "softap.*" -->
+    <string-array translatable="false" name="config_tether_wifi_regexs">
+        <item>"wlan\\d"</item>
+        <item>"softap\\d"</item>
+    </string-array>
+
+    <!-- List of regexpressions describing the interface (if any) that represent tetherable
+         Wifi P2P interfaces.  If the device doesn't want to support tethering over Wifi P2p this
+         should be empty.  An example would be "p2p-p2p.*" -->
+    <string-array translatable="false" name="config_tether_wifi_p2p_regexs">
+    </string-array>
+
+    <!-- List of regexpressions describing the interface (if any) that represent tetherable
+         bluetooth interfaces.  If the device doesn't want to support tethering over bluetooth this
+         should be empty. -->
+    <string-array translatable="false" name="config_tether_bluetooth_regexs">
+        <item>"bt-pan"</item>
+    </string-array>
+
+    <!-- Dhcp range (min, max) to use for tethering purposes -->
+    <string-array translatable="false" name="config_tether_dhcp_range">
+    </string-array>
+
+    <!-- Array of ConnectivityManager.TYPE_{BLUETOOTH, ETHERNET, MOBILE, MOBILE_DUN, MOBILE_HIPRI,
+         WIFI} values allowable for tethering.
+
+         Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
+         [1,7,0] for TYPE_WIFI, TYPE_BLUETOOTH, and TYPE_MOBILE.
+
+         This list is also modified by code within the framework, including:
+
+             - TYPE_ETHERNET (9) is prepended to this list, and
+
+             - the return value of TelephonyManager.isTetheringApnRequired()
+               determines how the array is further modified:
+
+                   * TRUE (DUN REQUIRED).
+                     TYPE_MOBILE is removed (if present).
+                     TYPE_MOBILE_HIPRI is removed (if present).
+                     TYPE_MOBILE_DUN is appended (if not already present).
+
+                   * FALSE (DUN NOT REQUIRED).
+                     TYPE_MOBILE_DUN is removed (if present).
+                     If both of TYPE_MOBILE{,_HIPRI} are not present:
+                        TYPE_MOBILE is appended.
+                        TYPE_MOBILE_HIPRI is appended.
+
+         For other changes applied to this list, now and in the future, see
+         com.android.server.connectivity.tethering.TetheringConfiguration.
+
+         Note also: the order of this is important. The first upstream type
+         for which a satisfying network exists is used.
+    -->
+    <integer-array translatable="false" name="config_tether_upstream_types">
+    </integer-array>
+
+    <!-- When true, the tethering upstream network follows the current default
+         Internet network (except when the current default network is mobile,
+         in which case a DUN network will be used if required).
+
+         When true, overrides the config_tether_upstream_types setting above.
+    -->
+    <bool translatable="false" name="config_tether_upstream_automatic">true</bool>
+
+
+    <!-- If the mobile hotspot feature requires provisioning, a package name and class name
+         can be provided to launch a supported application that provisions the devices.
+         EntitlementManager will send an inent to Settings with the specified package name and
+         class name in extras to launch provision app.
+         TODO: note what extras here.
+
+         See EntitlementManager#runUiTetherProvisioning and
+         packages/apps/Settings/src/com/android/settings/network/TetherProvisioningActivity.java
+         for more details.
+
+         For ui-less/periodic recheck support see config_mobile_hotspot_provision_app_no_ui
+        -->
+    <!-- The first element is the package name and the second element is the class name
+         of the provisioning app -->
+    <string-array translatable="false" name="config_mobile_hotspot_provision_app">
+    <!--
+        <item>com.example.provisioning</item>
+        <item>com.example.provisioning.Activity</item>
+    -->
+    </string-array>
+
+    <!-- If the mobile hotspot feature requires provisioning, an action can be provided
+         that will be broadcast in non-ui cases for checking the provisioning status.
+         EntitlementManager will pass the specified name to Settings and Settings would
+         launch provisioning app by sending an intent with the package name.
+
+         A second broadcast, action defined by config_mobile_hotspot_provision_response,
+         will be sent back to notify if provisioning succeeded or not.  The response will
+         match that of the activity in config_mobile_hotspot_provision_app, but instead
+         contained within the int extra "EntitlementResult".
+         TODO: provide the system api for "EntitlementResult" extra and note it here.
+
+         See EntitlementManager#runSilentTetherProvisioning and
+         packages/apps/Settings/src/com/android/settings/wifi/tether/TetherService.java for more
+         details.
+        -->
+    <string translatable="false" name="config_mobile_hotspot_provision_app_no_ui"></string>
+
+    <!-- Sent in response to a provisioning check. The caller must hold the
+         permission android.permission.TETHER_PRIVILEGED for Settings to
+         receive this response.
+
+         See config_mobile_hotspot_provision_response
+         -->
+    <string translatable="false" name="config_mobile_hotspot_provision_response"></string>
+
+    <!-- Number of hours between each background provisioning call -->
+    <integer translatable="false" name="config_mobile_hotspot_provision_check_period">24</integer>
+
+    <!-- ComponentName of the service used to run no ui tether provisioning. -->
+    <string translatable="false" name="config_wifi_tether_enable">com.android.settings/.wifi.tether.TetherService</string>
 </resources>
diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml
new file mode 100644
index 0000000..cc7980b
--- /dev/null
+++ b/packages/Tethering/res/values/overlayable.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <overlayable name="TetheringConfig">
+        <policy type="product|system|vendor">
+            <item type="array" name="config_tether_usb_regexs"/>
+            <item type="array" name="config_tether_wifi_regexs"/>
+            <item type="array" name="config_tether_wifi_p2p_regexs"/>
+            <item type="array" name="config_tether_bluetooth_regexs"/>
+            <item type="array" name="config_tether_dhcp_range"/>
+            <item type="array" name="config_tether_upstream_types"/>
+            <item type="bool" name="config_tether_upstream_automatic"/>
+            <!-- Configuration values for tethering entitlement check -->
+            <item type="array" name="config_mobile_hotspot_provision_app"/>
+            <item type="string" name="config_mobile_hotspot_provision_app_no_ui"/>
+            <item type="string" name="config_mobile_hotspot_provision_response"/>
+            <item type="integer" name="config_mobile_hotspot_provision_check_period"/>
+            <item type="string" name="config_wifi_tether_enable"/>
+        </policy>
+    </overlayable>
+</resources>
diff --git a/packages/Tethering/res/values/strings.xml b/packages/Tethering/res/values/strings.xml
index ca866a9..792bce9 100644
--- a/packages/Tethering/res/values/strings.xml
+++ b/packages/Tethering/res/values/strings.xml
@@ -1,4 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
 <resources>
     <!-- Shown when the device is tethered -->
     <!-- Strings for tethered notification title [CHAR LIMIT=200] -->
@@ -9,8 +23,11 @@
     <!-- This notification is shown when tethering has been disabled on a user's device.
     The device is managed by the user's employer. Tethering can't be turned on unless the
     IT administrator allows it. The noun "admin" is another reference for "IT administrator." -->
-    <!-- Strings for tether disabling notification title  [CHAR LIMIT=200] -->
+    <!-- Strings for tether disabling notification title [CHAR LIMIT=200] -->
     <string name="disable_tether_notification_title">Tethering is disabled</string>
-    <!-- Strings for tether disabling notification message  [CHAR LIMIT=200] -->
+    <!-- Strings for tether disabling notification message [CHAR LIMIT=200] -->
     <string name="disable_tether_notification_message">Contact your admin for details</string>
+
+    <!-- Strings for tether notification channel name [CHAR LIMIT=200] -->
+    <string name="notification_channel_tethering_status">Hotspot &amp; tethering status</string>
 </resources>
\ No newline at end of file
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index abfb33c..6ac467e 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -24,25 +24,24 @@
 import static android.net.util.NetworkConstants.asByte;
 import static android.net.util.TetheringMessageBase.BASE_IPSERVER;
 
-import android.net.ConnectivityManager;
 import android.net.INetd;
 import android.net.INetworkStackStatusCallback;
 import android.net.INetworkStatsService;
-import android.net.InterfaceConfiguration;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.RouteInfo;
+import android.net.TetheringManager;
 import android.net.dhcp.DhcpServerCallbacks;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.dhcp.DhcpServingParamsParcelExt;
 import android.net.dhcp.IDhcpServer;
 import android.net.ip.RouterAdvertisementDaemon.RaParams;
+import android.net.shared.NetdUtils;
+import android.net.shared.RouteUtils;
 import android.net.util.InterfaceParams;
 import android.net.util.InterfaceSet;
-import android.net.util.NetdService;
 import android.net.util.SharedLog;
-import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
@@ -119,7 +118,7 @@
          *
          * @param who the calling instance of IpServer
          * @param state one of STATE_*
-         * @param lastError one of ConnectivityManager.TETHER_ERROR_*
+         * @param lastError one of TetheringManager.TETHER_ERROR_*
          */
         public void updateInterfaceState(IpServer who, int state, int lastError) { }
 
@@ -144,10 +143,6 @@
             return InterfaceParams.getByName(ifName);
         }
 
-        public INetd getNetdService() {
-            return NetdService.getInstance();
-        }
-
         /** Create a DhcpServer instance to be used by IpServer. */
         public abstract void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
                 DhcpServerCallbacks cb);
@@ -180,7 +175,6 @@
     private final State mUnavailableState;
 
     private final SharedLog mLog;
-    private final INetworkManagementService mNMService;
     private final INetd mNetd;
     private final INetworkStatsService mStatsService;
     private final Callback mCallback;
@@ -210,15 +204,15 @@
     private int mDhcpServerStartIndex = 0;
     private IDhcpServer mDhcpServer;
     private RaParams mLastRaParams;
+    private LinkAddress mIpv4Address;
 
     public IpServer(
             String ifaceName, Looper looper, int interfaceType, SharedLog log,
-            INetworkManagementService nMService, INetworkStatsService statsService,
-            Callback callback, boolean usingLegacyDhcp, Dependencies deps) {
+            INetd netd, INetworkStatsService statsService, Callback callback,
+            boolean usingLegacyDhcp, Dependencies deps) {
         super(ifaceName, looper);
         mLog = log.forSubComponent(ifaceName);
-        mNMService = nMService;
-        mNetd = deps.getNetdService();
+        mNetd = netd;
         mStatsService = statsService;
         mCallback = callback;
         mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog);
@@ -228,7 +222,7 @@
         mUsingLegacyDhcp = usingLegacyDhcp;
         mDeps = deps;
         resetLinkProperties();
-        mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+        mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
         mServingMode = STATE_AVAILABLE;
 
         mInitialState = new InitialState();
@@ -249,7 +243,7 @@
     }
 
     /**
-     * Tethering downstream type. It would be one of ConnectivityManager#TETHERING_*.
+     * Tethering downstream type. It would be one of TetheringManager#TETHERING_*.
      */
     public int interfaceType() {
         return mInterfaceType;
@@ -347,13 +341,13 @@
                         }
                     });
                 } catch (RemoteException e) {
-                    e.rethrowFromSystemServer();
+                    throw new IllegalStateException(e);
                 }
             });
         }
 
         private void handleError() {
-            mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR;
+            mLastError = TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
             transitionTo(mInitialState);
         }
     }
@@ -388,14 +382,15 @@
                     public void callback(int statusCode) {
                         if (statusCode != STATUS_SUCCESS) {
                             mLog.e("Error stopping DHCP server: " + statusCode);
-                            mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR;
+                            mLastError = TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
                             // Not much more we can do here
                         }
                     }
                 });
                 mDhcpServer = null;
             } catch (RemoteException e) {
-                e.rethrowFromSystemServer();
+                mLog.e("Error stopping DHCP", e);
+                // Not much more we can do here
             }
         }
     }
@@ -414,85 +409,69 @@
         // NOTE: All of configureIPv4() will be refactored out of existence
         // into calls to InterfaceController, shared with startIPv4().
         mInterfaceCtrl.clearIPv4Address();
+        mIpv4Address = null;
     }
 
-    // TODO: Refactor this in terms of calls to InterfaceController.
     private boolean configureIPv4(boolean enabled) {
         if (VDBG) Log.d(TAG, "configureIPv4(" + enabled + ")");
 
         // TODO: Replace this hard-coded information with dynamically selected
         // config passed down to us by a higher layer IP-coordinating element.
-        String ipAsString = null;
+        final Inet4Address srvAddr;
         int prefixLen = 0;
-        if (mInterfaceType == ConnectivityManager.TETHERING_USB) {
-            ipAsString = USB_NEAR_IFACE_ADDR;
-            prefixLen = USB_PREFIX_LENGTH;
-        } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
-            ipAsString = getRandomWifiIPv4Address();
-            prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH;
-        } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI_P2P) {
-            ipAsString = WIFI_P2P_IFACE_ADDR;
-            prefixLen = WIFI_P2P_IFACE_PREFIX_LENGTH;
-        } else {
-            // BT configures the interface elsewhere: only start DHCP.
-            final Inet4Address srvAddr = (Inet4Address) parseNumericAddress(BLUETOOTH_IFACE_ADDR);
-            return configureDhcp(enabled, srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
+        try {
+            if (mInterfaceType == TetheringManager.TETHERING_USB) {
+                srvAddr = (Inet4Address) parseNumericAddress(USB_NEAR_IFACE_ADDR);
+                prefixLen = USB_PREFIX_LENGTH;
+            } else if (mInterfaceType == TetheringManager.TETHERING_WIFI) {
+                srvAddr = (Inet4Address) parseNumericAddress(getRandomWifiIPv4Address());
+                prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH;
+            } else if (mInterfaceType == TetheringManager.TETHERING_WIFI_P2P) {
+                srvAddr = (Inet4Address) parseNumericAddress(WIFI_P2P_IFACE_ADDR);
+                prefixLen = WIFI_P2P_IFACE_PREFIX_LENGTH;
+            } else {
+                // BT configures the interface elsewhere: only start DHCP.
+                // TODO: make all tethering types behave the same way, and delete the bluetooth
+                // code that calls into NetworkManagementService directly.
+                srvAddr = (Inet4Address) parseNumericAddress(BLUETOOTH_IFACE_ADDR);
+                mIpv4Address = new LinkAddress(srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
+                return configureDhcp(enabled, srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
+            }
+            mIpv4Address = new LinkAddress(srvAddr, prefixLen);
+        } catch (IllegalArgumentException e) {
+            mLog.e("Error selecting ipv4 address", e);
+            if (!enabled) stopDhcp();
+            return false;
         }
 
-        final LinkAddress linkAddr;
-        try {
-            final InterfaceConfiguration ifcg = mNMService.getInterfaceConfig(mIfaceName);
-            if (ifcg == null) {
-                mLog.e("Received null interface config");
-                return false;
-            }
+        final Boolean setIfaceUp;
+        if (mInterfaceType == TetheringManager.TETHERING_WIFI) {
+            // The WiFi stack has ownership of the interface up/down state.
+            // It is unclear whether the Bluetooth or USB stacks will manage their own
+            // state.
+            setIfaceUp = null;
+        } else {
+            setIfaceUp = enabled;
+        }
+        if (!mInterfaceCtrl.setInterfaceConfiguration(mIpv4Address, setIfaceUp)) {
+            mLog.e("Error configuring interface");
+            if (!enabled) stopDhcp();
+            return false;
+        }
 
-            InetAddress addr = parseNumericAddress(ipAsString);
-            linkAddr = new LinkAddress(addr, prefixLen);
-            ifcg.setLinkAddress(linkAddr);
-            if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
-                // The WiFi stack has ownership of the interface up/down state.
-                // It is unclear whether the Bluetooth or USB stacks will manage their own
-                // state.
-                ifcg.ignoreInterfaceUpDownStatus();
-            } else {
-                if (enabled) {
-                    ifcg.setInterfaceUp();
-                } else {
-                    ifcg.setInterfaceDown();
-                }
-            }
-            ifcg.clearFlag("running");
-
-            // TODO: this may throw if the interface is already gone. Do proper handling and
-            // simplify the DHCP server start/stop.
-            mNMService.setInterfaceConfig(mIfaceName, ifcg);
-
-            if (!configureDhcp(enabled, (Inet4Address) addr, prefixLen)) {
-                return false;
-            }
-        } catch (Exception e) {
-            mLog.e("Error configuring interface " + e);
-            if (!enabled) {
-                try {
-                    // Calling stopDhcp several times is fine
-                    stopDhcp();
-                } catch (Exception dhcpError) {
-                    mLog.e("Error stopping DHCP", dhcpError);
-                }
-            }
+        if (!configureDhcp(enabled, srvAddr, prefixLen)) {
             return false;
         }
 
         // Directly-connected route.
-        final IpPrefix ipv4Prefix = new IpPrefix(linkAddr.getAddress(),
-                linkAddr.getPrefixLength());
+        final IpPrefix ipv4Prefix = new IpPrefix(mIpv4Address.getAddress(),
+                mIpv4Address.getPrefixLength());
         final RouteInfo route = new RouteInfo(ipv4Prefix, null, null, RTN_UNICAST);
         if (enabled) {
-            mLinkProperties.addLinkAddress(linkAddr);
+            mLinkProperties.addLinkAddress(mIpv4Address);
             mLinkProperties.addRoute(route);
         } else {
-            mLinkProperties.removeLinkAddress(linkAddr);
+            mLinkProperties.removeLinkAddress(mIpv4Address);
             mLinkProperties.removeRoute(route);
         }
         return true;
@@ -584,14 +563,12 @@
         if (!deprecatedPrefixes.isEmpty()) {
             final ArrayList<RouteInfo> toBeRemoved =
                     getLocalRoutesFor(mIfaceName, deprecatedPrefixes);
-            try {
-                final int removalFailures = mNMService.removeRoutesFromLocalNetwork(toBeRemoved);
-                if (removalFailures > 0) {
-                    mLog.e(String.format("Failed to remove %d IPv6 routes from local table.",
-                            removalFailures));
-                }
-            } catch (RemoteException e) {
-                mLog.e("Failed to remove IPv6 routes from local table: " + e);
+            // Remove routes from local network.
+            final int removalFailures = RouteUtils.removeRoutesFromLocalNetwork(
+                    mNetd, toBeRemoved);
+            if (removalFailures > 0) {
+                mLog.e(String.format("Failed to remove %d IPv6 routes from local table.",
+                        removalFailures));
             }
 
             for (RouteInfo route : toBeRemoved) mLinkProperties.removeRoute(route);
@@ -608,13 +585,18 @@
                 final ArrayList<RouteInfo> toBeAdded =
                         getLocalRoutesFor(mIfaceName, addedPrefixes);
                 try {
-                    // It's safe to call addInterfaceToLocalNetwork() even if
-                    // the interface is already in the local_network. Note also
-                    // that adding routes that already exist does not cause an
-                    // error (EEXIST is silently ignored).
-                    mNMService.addInterfaceToLocalNetwork(mIfaceName, toBeAdded);
-                } catch (Exception e) {
-                    mLog.e("Failed to add IPv6 routes to local table: " + e);
+                    // It's safe to call networkAddInterface() even if
+                    // the interface is already in the local_network.
+                    mNetd.networkAddInterface(INetd.LOCAL_NET_ID, mIfaceName);
+                    try {
+                        // Add routes from local network. Note that adding routes that
+                        // already exist does not cause an error (EEXIST is silently ignored).
+                        RouteUtils.addRoutesToLocalNetwork(mNetd, mIfaceName, toBeAdded);
+                    } catch (IllegalStateException e) {
+                        mLog.e("Failed to add IPv6 routes to local table: " + e);
+                    }
+                } catch (ServiceSpecificException | RemoteException e) {
+                    mLog.e("Failed to add " + mIfaceName + " to local table: ", e);
                 }
 
                 for (RouteInfo route : toBeAdded) mLinkProperties.addRoute(route);
@@ -728,7 +710,7 @@
             logMessage(this, message.what);
             switch (message.what) {
                 case CMD_TETHER_REQUESTED:
-                    mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+                    mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
                     switch (message.arg1) {
                         case STATE_LOCAL_ONLY:
                             transitionTo(mLocalHotspotState);
@@ -757,15 +739,17 @@
         @Override
         public void enter() {
             if (!startIPv4()) {
-                mLastError = ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR;
+                mLastError = TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
                 return;
             }
 
             try {
-                mNMService.tetherInterface(mIfaceName);
-            } catch (Exception e) {
+                final IpPrefix ipv4Prefix = new IpPrefix(mIpv4Address.getAddress(),
+                        mIpv4Address.getPrefixLength());
+                NetdUtils.tetherInterface(mNetd, mIfaceName, ipv4Prefix);
+            } catch (RemoteException | ServiceSpecificException e) {
                 mLog.e("Error Tethering: " + e);
-                mLastError = ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+                mLastError = TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
                 return;
             }
 
@@ -784,9 +768,9 @@
             stopIPv6();
 
             try {
-                mNMService.untetherInterface(mIfaceName);
-            } catch (Exception e) {
-                mLastError = ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+                NetdUtils.untetherInterface(mNetd, mIfaceName);
+            } catch (RemoteException | ServiceSpecificException e) {
+                mLastError = TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
                 mLog.e("Failed to untether interface: " + e);
             }
 
@@ -816,7 +800,7 @@
                 case CMD_START_TETHERING_ERROR:
                 case CMD_STOP_TETHERING_ERROR:
                 case CMD_SET_DNS_FORWARDERS_ERROR:
-                    mLastError = ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
+                    mLastError = TetheringManager.TETHER_ERROR_MASTER_ERROR;
                     transitionTo(mInitialState);
                     break;
                 default:
@@ -835,7 +819,7 @@
         @Override
         public void enter() {
             super.enter();
-            if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+            if (mLastError != TetheringManager.TETHER_ERROR_NO_ERROR) {
                 transitionTo(mInitialState);
             }
 
@@ -871,7 +855,7 @@
         @Override
         public void enter() {
             super.enter();
-            if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+            if (mLastError != TetheringManager.TETHER_ERROR_NO_ERROR) {
                 transitionTo(mInitialState);
             }
 
@@ -901,17 +885,17 @@
                 // About to tear down NAT; gather remaining statistics.
                 mStatsService.forceUpdate();
             } catch (Exception e) {
-                if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
+                mLog.e("Exception in forceUpdate: " + e.toString());
             }
             try {
-                mNMService.stopInterfaceForwarding(mIfaceName, upstreamIface);
-            } catch (Exception e) {
-                if (VDBG) Log.e(TAG, "Exception in removeInterfaceForward: " + e.toString());
+                mNetd.ipfwdRemoveInterfaceForward(mIfaceName, upstreamIface);
+            } catch (RemoteException | ServiceSpecificException e) {
+                mLog.e("Exception in ipfwdRemoveInterfaceForward: " + e.toString());
             }
             try {
-                mNMService.disableNat(mIfaceName, upstreamIface);
-            } catch (Exception e) {
-                if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
+                mNetd.tetherRemoveForward(mIfaceName, upstreamIface);
+            } catch (RemoteException | ServiceSpecificException e) {
+                mLog.e("Exception in disableNat: " + e.toString());
             }
         }
 
@@ -947,12 +931,12 @@
 
                     for (String ifname : added) {
                         try {
-                            mNMService.enableNat(mIfaceName, ifname);
-                            mNMService.startInterfaceForwarding(mIfaceName, ifname);
-                        } catch (Exception e) {
-                            mLog.e("Exception enabling NAT: " + e);
+                            mNetd.tetherAddForward(mIfaceName, ifname);
+                            mNetd.ipfwdAddInterfaceForward(mIfaceName, ifname);
+                        } catch (RemoteException | ServiceSpecificException e) {
+                            mLog.e("Exception enabling NAT: " + e.toString());
                             cleanupUpstream();
-                            mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+                            mLastError = TetheringManager.TETHER_ERROR_ENABLE_NAT_ERROR;
                             transitionTo(mInitialState);
                             return true;
                         }
@@ -997,7 +981,7 @@
     class UnavailableState extends State {
         @Override
         public void enter() {
-            mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+            mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
             sendInterfaceState(STATE_UNAVAILABLE);
         }
     }
diff --git a/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
index bba61d7..6f017dc 100644
--- a/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
+++ b/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
@@ -668,7 +668,7 @@
     }
 
     private final class UnicastResponder extends Thread {
-        private final InetSocketAddress mSolicitor = new InetSocketAddress();
+        private final InetSocketAddress mSolicitor = new InetSocketAddress(0);
         // The recycled buffer for receiving Router Solicitations from clients.
         // If the RS is larger than IPV6_MIN_MTU the packets are truncated.
         // This is fine since currently only byte 0 is examined anyway.
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
index 7e685fb..4e2a2c1 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
@@ -16,16 +16,16 @@
 
 package com.android.server.connectivity.tethering;
 
-import static android.net.ConnectivityManager.EXTRA_ADD_TETHER_TYPE;
-import static android.net.ConnectivityManager.EXTRA_PROVISION_CALLBACK;
-import static android.net.ConnectivityManager.EXTRA_RUN_PROVISION;
-import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
-import static android.net.ConnectivityManager.TETHERING_INVALID;
-import static android.net.ConnectivityManager.TETHERING_USB;
-import static android.net.ConnectivityManager.TETHERING_WIFI;
-import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN;
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED;
+import static android.net.TetheringManager.EXTRA_ADD_TETHER_TYPE;
+import static android.net.TetheringManager.EXTRA_PROVISION_CALLBACK;
+import static android.net.TetheringManager.EXTRA_RUN_PROVISION;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_INVALID;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKONWN;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED;
 
 import static com.android.internal.R.string.config_wifi_tether_enable;
 
@@ -59,7 +59,7 @@
 
 /**
  * Re-check tethering provisioning for enabled downstream tether types.
- * Reference ConnectivityManager.TETHERING_{@code *} for each tether type.
+ * Reference TetheringManager.TETHERING_{@code *} for each tether type.
  *
  * All methods of this class must be accessed from the thread of tethering
  * state machine.
@@ -86,9 +86,9 @@
     private static final int EVENT_GET_ENTITLEMENT_VALUE = 4;
 
     // The ArraySet contains enabled downstream types, ex:
-    // {@link ConnectivityManager.TETHERING_WIFI}
-    // {@link ConnectivityManager.TETHERING_USB}
-    // {@link ConnectivityManager.TETHERING_BLUETOOTH}
+    // {@link TetheringManager.TETHERING_WIFI}
+    // {@link TetheringManager.TETHERING_USB}
+    // {@link TetheringManager.TETHERING_BLUETOOTH}
     private final ArraySet<Integer> mCurrentTethers;
     private final Context mContext;
     private final int mPermissionChangeMessageCode;
@@ -96,8 +96,8 @@
     private final SparseIntArray mEntitlementCacheValue;
     private final EntitlementHandler mHandler;
     private final StateMachine mTetherMasterSM;
-    // Key: ConnectivityManager.TETHERING_*(downstream).
-    // Value: ConnectivityManager.TETHER_ERROR_{NO_ERROR or PROVISION_FAILED}(provisioning result).
+    // Key: TetheringManager.TETHERING_*(downstream).
+    // Value: TetheringManager.TETHER_ERROR_{NO_ERROR or PROVISION_FAILED}(provisioning result).
     private final SparseIntArray mCellularPermitted;
     private PendingIntent mProvisioningRecheckAlarm;
     private boolean mCellularUpstreamPermitted = true;
@@ -133,7 +133,7 @@
         /**
          * Ui entitlement check fails in |downstream|.
          *
-         * @param downstream tethering type from ConnectivityManager.TETHERING_{@code *}.
+         * @param downstream tethering type from TetheringManager.TETHERING_{@code *}.
          */
         void onUiEntitlementFailed(int downstream);
     }
@@ -169,7 +169,7 @@
      * This is called when tethering starts.
      * Launch provisioning app if upstream is cellular.
      *
-     * @param downstreamType tethering type from ConnectivityManager.TETHERING_{@code *}
+     * @param downstreamType tethering type from TetheringManager.TETHERING_{@code *}
      * @param showProvisioningUi a boolean indicating whether to show the
      *        provisioning app UI if there is one.
      */
@@ -210,7 +210,7 @@
     /**
      * Tell EntitlementManager that a given type of tethering has been disabled
      *
-     * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+     * @param type tethering type from TetheringManager.TETHERING_{@code *}
      */
     public void stopProvisioningIfNeeded(int type) {
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_STOP_PROVISIONING, type, 0));
@@ -296,7 +296,7 @@
 
     /**
      * Re-check tethering provisioning for all enabled tether types.
-     * Reference ConnectivityManager.TETHERING_{@code *} for each tether type.
+     * Reference TetheringManager.TETHERING_{@code *} for each tether type.
      *
      * @param config an object that encapsulates the various tethering configuration elements.
      * Note: this method is only called from TetherMaster on the handler thread.
@@ -363,7 +363,7 @@
 
     /**
      * Run no UI tethering provisioning check.
-     * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+     * @param type tethering type from TetheringManager.TETHERING_{@code *}
      * @param subId default data subscription ID.
      */
     @VisibleForTesting
@@ -390,7 +390,7 @@
 
     /**
      * Run the UI-enabled tethering provisioning check.
-     * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+     * @param type tethering type from TetheringManager.TETHERING_{@code *}
      * @param subId default data subscription ID.
      * @param receiver to receive entitlement check result.
      */
@@ -398,7 +398,7 @@
     protected void runUiTetherProvisioning(int type, int subId, ResultReceiver receiver) {
         if (DBG) mLog.i("runUiTetherProvisioning: " + type);
 
-        Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING);
+        Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING_UI);
         intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
         intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
         intent.putExtra(EXTRA_SUBID, subId);
@@ -461,7 +461,7 @@
      * Add the mapping between provisioning result and tethering type.
      * Notify UpstreamNetworkMonitor if Cellular permission changes.
      *
-     * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+     * @param type tethering type from TetheringManager.TETHERING_{@code *}
      * @param resultCode Provisioning result
      */
     protected void addDownstreamMapping(int type, int resultCode) {
@@ -476,7 +476,7 @@
 
     /**
      * Remove the mapping for input tethering type.
-     * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+     * @param type tethering type from TetheringManager.TETHERING_{@code *}
      */
     protected void removeDownstreamMapping(int type) {
         mLog.i("removeDownstreamMapping: " + type);
@@ -625,7 +625,7 @@
     /**
      * Update the last entitlement value to internal cache
      *
-     * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+     * @param type tethering type from TetheringManager.TETHERING_{@code *}
      * @param resultCode last entitlement value
      * @return the last updated entitlement value
      */
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
index 38fa91e..ce7c2a6 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
@@ -279,7 +279,7 @@
                 entry.iface = kv.getKey();
                 entry.rxBytes = value.rxBytes;
                 entry.txBytes = value.txBytes;
-                stats.addValues(entry);
+                stats.addEntry(entry);
             }
 
             return stats;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index 5b26704..038d7ae 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -20,23 +20,23 @@
 import static android.hardware.usb.UsbManager.USB_CONFIGURED;
 import static android.hardware.usb.UsbManager.USB_CONNECTED;
 import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
-import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
-import static android.net.ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY;
-import static android.net.ConnectivityManager.EXTRA_ACTIVE_TETHER;
-import static android.net.ConnectivityManager.EXTRA_AVAILABLE_TETHER;
-import static android.net.ConnectivityManager.EXTRA_ERRORED_TETHER;
 import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
-import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
-import static android.net.ConnectivityManager.TETHERING_INVALID;
-import static android.net.ConnectivityManager.TETHERING_USB;
-import static android.net.ConnectivityManager.TETHERING_WIFI;
-import static android.net.ConnectivityManager.TETHERING_WIFI_P2P;
-import static android.net.ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_SERVICE_UNAVAIL;
-import static android.net.ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
-import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
+import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
+import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
+import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER;
+import static android.net.TetheringManager.EXTRA_AVAILABLE_TETHER;
+import static android.net.TetheringManager.EXTRA_ERRORED_TETHER;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_INVALID;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_MASTER_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
+import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
 import static android.net.util.TetheringMessageBase.BASE_MASTER;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
@@ -50,6 +50,7 @@
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import android.app.Notification;
+import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
@@ -71,10 +72,10 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkInfo;
-import android.net.NetworkUtils;
 import android.net.TetherStatesParcel;
 import android.net.TetheringConfigurationParcel;
 import android.net.ip.IpServer;
+import android.net.shared.NetdUtils;
 import android.net.util.BaseNetdUnsolicitedEventListener;
 import android.net.util.InterfaceSet;
 import android.net.util.PrefixUtils;
@@ -87,12 +88,12 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceSpecificException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.telephony.PhoneStateListener;
@@ -102,9 +103,10 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.State;
@@ -139,6 +141,8 @@
     };
     private static final SparseArray<String> sMagicDecoderRing =
             MessageUtils.findMessageNames(sMessageClasses);
+    // Keep in sync with NETID_UNSET in system/netd/include/netid_client.h
+    private static final int NETID_UNSET = 0;
 
     private static class TetherState {
         public final IpServer ipServer;
@@ -172,8 +176,6 @@
     private final Context mContext;
     private final ArrayMap<String, TetherState> mTetherStates;
     private final BroadcastReceiver mStateReceiver;
-    // Stopship: replace mNMService before production.
-    private final INetworkManagementService mNMService;
     private final INetworkStatsService mStatsService;
     private final INetworkPolicyManager mPolicyManager;
     private final Looper mLooper;
@@ -210,7 +212,6 @@
         mLog.mark("Tethering.constructed");
         mDeps = deps;
         mContext = mDeps.getContext();
-        mNMService = mDeps.getINetworkManagementService();
         mStatsService = mDeps.getINetworkStatsService();
         mPolicyManager = mDeps.getINetworkPolicyManager();
         mNetd = mDeps.getINetd(mContext);
@@ -225,10 +226,9 @@
 
         mHandler = mTetherMasterSM.getHandler();
         mOffloadController = new OffloadController(mHandler,
-                mDeps.getOffloadHardwareInterface(mHandler, mLog),
-                mContext.getContentResolver(), mNMService,
-                mLog);
-        mUpstreamNetworkMonitor = deps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
+                mDeps.getOffloadHardwareInterface(mHandler, mLog), mContext.getContentResolver(),
+                mDeps.getINetworkManagementService(), mLog);
+        mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
                 TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
         mForwardedDownstreams = new HashSet<>();
 
@@ -289,13 +289,6 @@
         filter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
         filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
         mContext.registerReceiver(mStateReceiver, filter, null, handler);
-
-        filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_MEDIA_SHARED);
-        filter.addAction(Intent.ACTION_MEDIA_UNSHARED);
-        filter.addDataScheme("file");
-        mContext.registerReceiver(mStateReceiver, filter, null, handler);
-
     }
 
     private class TetheringThreadExecutor implements Executor {
@@ -421,7 +414,6 @@
         }
     }
 
-
     void interfaceRemoved(String iface) {
         if (VDBG) Log.d(TAG, "interfaceRemoved " + iface);
         synchronized (mPublicSync) {
@@ -670,19 +662,19 @@
 
         if (usbTethered) {
             if (wifiTethered || bluetoothTethered) {
-                showTetheredNotification(SystemMessage.NOTE_TETHER_GENERAL);
+                showTetheredNotification(R.drawable.stat_sys_tether_general);
             } else {
-                showTetheredNotification(SystemMessage.NOTE_TETHER_USB);
+                showTetheredNotification(R.drawable.stat_sys_tether_usb);
             }
         } else if (wifiTethered) {
             if (bluetoothTethered) {
-                showTetheredNotification(SystemMessage.NOTE_TETHER_GENERAL);
+                showTetheredNotification(R.drawable.stat_sys_tether_general);
             } else {
                 /* We now have a status bar icon for WifiTethering, so drop the notification */
                 clearTetheredNotification();
             }
         } else if (bluetoothTethered) {
-            showTetheredNotification(SystemMessage.NOTE_TETHER_BLUETOOTH);
+            showTetheredNotification(R.drawable.stat_sys_tether_bluetooth);
         } else {
             clearTetheredNotification();
         }
@@ -695,30 +687,22 @@
     @VisibleForTesting
     protected void showTetheredNotification(int id, boolean tetheringOn) {
         NotificationManager notificationManager =
-                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+                (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0)
+                        .getSystemService(Context.NOTIFICATION_SERVICE);
         if (notificationManager == null) {
             return;
         }
-        int icon = 0;
-        switch(id) {
-            case SystemMessage.NOTE_TETHER_USB:
-                icon = R.drawable.stat_sys_tether_usb;
-                break;
-            case SystemMessage.NOTE_TETHER_BLUETOOTH:
-                icon = R.drawable.stat_sys_tether_bluetooth;
-                break;
-            case SystemMessage.NOTE_TETHER_GENERAL:
-            default:
-                icon = R.drawable.stat_sys_tether_general;
-                break;
-        }
+        final NotificationChannel channel = new NotificationChannel(
+                "TETHERING_STATUS",
+                mContext.getResources().getString(R.string.notification_channel_tethering_status),
+                NotificationManager.IMPORTANCE_LOW);
+        notificationManager.createNotificationChannel(channel);
 
         if (mLastNotificationId != 0) {
-            if (mLastNotificationId == icon) {
+            if (mLastNotificationId == id) {
                 return;
             }
-            notificationManager.cancelAsUser(null, mLastNotificationId,
-                    UserHandle.ALL);
+            notificationManager.cancel(null, mLastNotificationId);
             mLastNotificationId = 0;
         }
 
@@ -726,8 +710,8 @@
         intent.setClassName("com.android.settings", "com.android.settings.TetherSettings");
         intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
 
-        PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, intent, 0,
-                null, UserHandle.CURRENT);
+        PendingIntent pi = PendingIntent.getActivity(
+                mContext.createContextAsUser(UserHandle.CURRENT, 0), 0, intent, 0, null);
 
         Resources r = mContext.getResources();
         final CharSequence title;
@@ -742,32 +726,31 @@
         }
 
         if (mTetheredNotificationBuilder == null) {
-            mTetheredNotificationBuilder = new Notification.Builder(mContext,
-                    SystemNotificationChannels.NETWORK_STATUS);
+            mTetheredNotificationBuilder = new Notification.Builder(mContext, channel.getId());
             mTetheredNotificationBuilder.setWhen(0)
                     .setOngoing(true)
                     .setColor(mContext.getColor(
-                            com.android.internal.R.color.system_notification_accent_color))
+                            android.R.color.system_notification_accent_color))
                     .setVisibility(Notification.VISIBILITY_PUBLIC)
                     .setCategory(Notification.CATEGORY_STATUS);
         }
-        mTetheredNotificationBuilder.setSmallIcon(icon)
+        mTetheredNotificationBuilder.setSmallIcon(id)
                 .setContentTitle(title)
                 .setContentText(message)
                 .setContentIntent(pi);
         mLastNotificationId = id;
 
-        notificationManager.notifyAsUser(null, mLastNotificationId,
-                mTetheredNotificationBuilder.buildInto(new Notification()), UserHandle.ALL);
+        notificationManager.notify(null, mLastNotificationId,
+                mTetheredNotificationBuilder.buildInto(new Notification()));
     }
 
     @VisibleForTesting
     protected void clearTetheredNotification() {
         NotificationManager notificationManager =
-                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+                (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0)
+                        .getSystemService(Context.NOTIFICATION_SERVICE);
         if (notificationManager != null && mLastNotificationId != 0) {
-            notificationManager.cancelAsUser(null, mLastNotificationId,
-                    UserHandle.ALL);
+            notificationManager.cancel(null, mLastNotificationId);
             mLastNotificationId = 0;
         }
     }
@@ -1022,8 +1005,8 @@
 
         String[] ifaces = null;
         try {
-            ifaces = mNMService.listInterfaces();
-        } catch (Exception e) {
+            ifaces = mNetd.interfaceGetList();
+        } catch (RemoteException | ServiceSpecificException e) {
             Log.e(TAG, "Error listing Interfaces", e);
             return;
         }
@@ -1282,25 +1265,25 @@
         protected boolean turnOnMasterTetherSettings() {
             final TetheringConfiguration cfg = mConfig;
             try {
-                mNMService.setIpForwardingEnabled(true);
-            } catch (Exception e) {
+                mNetd.ipfwdEnableForwarding(TAG);
+            } catch (RemoteException | ServiceSpecificException e) {
                 mLog.e(e);
                 transitionTo(mSetIpForwardingEnabledErrorState);
                 return false;
             }
+
             // TODO: Randomize DHCPv4 ranges, especially in hotspot mode.
             // Legacy DHCP server is disabled if passed an empty ranges array
             final String[] dhcpRanges = cfg.enableLegacyDhcpServer
-                    ? cfg.legacyDhcpRanges
-                    : new String[0];
+                    ? cfg.legacyDhcpRanges : new String[0];
             try {
-                // TODO: Find a more accurate method name (startDHCPv4()?).
-                mNMService.startTethering(dhcpRanges);
-            } catch (Exception e) {
+                NetdUtils.tetherStart(mNetd, true /** usingLegacyDnsProxy */, dhcpRanges);
+            } catch (RemoteException | ServiceSpecificException e) {
                 try {
-                    mNMService.stopTethering();
-                    mNMService.startTethering(dhcpRanges);
-                } catch (Exception ee) {
+                    // Stop and retry.
+                    mNetd.tetherStop();
+                    NetdUtils.tetherStart(mNetd, true /** usingLegacyDnsProxy */, dhcpRanges);
+                } catch (RemoteException | ServiceSpecificException ee) {
                     mLog.e(ee);
                     transitionTo(mStartTetheringErrorState);
                     return false;
@@ -1312,15 +1295,15 @@
 
         protected boolean turnOffMasterTetherSettings() {
             try {
-                mNMService.stopTethering();
-            } catch (Exception e) {
+                mNetd.tetherStop();
+            } catch (RemoteException | ServiceSpecificException e) {
                 mLog.e(e);
                 transitionTo(mStopTetheringErrorState);
                 return false;
             }
             try {
-                mNMService.setIpForwardingEnabled(false);
-            } catch (Exception e) {
+                mNetd.ipfwdDisableForwarding(TAG);
+            } catch (RemoteException | ServiceSpecificException e) {
                 mLog.e(e);
                 transitionTo(mSetIpForwardingDisabledErrorState);
                 return false;
@@ -1383,19 +1366,25 @@
 
         protected void setDnsForwarders(final Network network, final LinkProperties lp) {
             // TODO: Set v4 and/or v6 DNS per available connectivity.
-            String[] dnsServers = mConfig.defaultIPv4DNS;
             final Collection<InetAddress> dnses = lp.getDnsServers();
             // TODO: Properly support the absence of DNS servers.
+            final String[] dnsServers;
             if (dnses != null && !dnses.isEmpty()) {
-                // TODO: remove this invocation of NetworkUtils.makeStrings().
-                dnsServers = NetworkUtils.makeStrings(dnses);
+                dnsServers = new String[dnses.size()];
+                int i = 0;
+                for (InetAddress dns : dnses) {
+                    dnsServers[i++] = dns.getHostAddress();
+                }
+            } else {
+                dnsServers = mConfig.defaultIPv4DNS;
             }
+            final int netId = (network != null) ? network.netId : NETID_UNSET;
             try {
-                mNMService.setDnsForwarders(network, dnsServers);
+                mNetd.tetherDnsSet(netId, dnsServers);
                 mLog.log(String.format(
                         "SET DNS forwarders: network=%s dnsServers=%s",
                         network, Arrays.toString(dnsServers)));
-            } catch (Exception e) {
+            } catch (RemoteException | ServiceSpecificException e) {
                 // TODO: Investigate how this can fail and what exactly
                 // happens if/when such failures occur.
                 mLog.e("setting DNS forwarders failed, " + e);
@@ -1698,8 +1687,8 @@
                 Log.e(TAG, "Error in startTethering");
                 notify(IpServer.CMD_START_TETHERING_ERROR);
                 try {
-                    mNMService.setIpForwardingEnabled(false);
-                } catch (Exception e) { }
+                    mNetd.ipfwdDisableForwarding(TAG);
+                } catch (RemoteException | ServiceSpecificException e) { }
             }
         }
 
@@ -1709,8 +1698,8 @@
                 Log.e(TAG, "Error in stopTethering");
                 notify(IpServer.CMD_STOP_TETHERING_ERROR);
                 try {
-                    mNMService.setIpForwardingEnabled(false);
-                } catch (Exception e) { }
+                    mNetd.ipfwdDisableForwarding(TAG);
+                } catch (RemoteException | ServiceSpecificException e) { }
             }
         }
 
@@ -1720,11 +1709,11 @@
                 Log.e(TAG, "Error in setDnsForwarders");
                 notify(IpServer.CMD_SET_DNS_FORWARDERS_ERROR);
                 try {
-                    mNMService.stopTethering();
-                } catch (Exception e) { }
+                    mNetd.tetherStop();
+                } catch (RemoteException | ServiceSpecificException e) { }
                 try {
-                    mNMService.setIpForwardingEnabled(false);
-                } catch (Exception e) { }
+                    mNetd.ipfwdDisableForwarding(TAG);
+                } catch (RemoteException | ServiceSpecificException e) { }
             }
         }
 
@@ -1884,7 +1873,7 @@
         }
     }
 
-    void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+    void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) {
         // Binder.java closes the resource for us.
         @SuppressWarnings("resource")
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
@@ -2065,7 +2054,7 @@
 
         mLog.log("adding TetheringInterfaceStateMachine for: " + iface);
         final TetherState tetherState = new TetherState(
-                new IpServer(iface, mLooper, interfaceType, mLog, mNMService, mStatsService,
+                new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mStatsService,
                              makeControlCallback(), mConfig.enableLegacyDhcpServer,
                              mDeps.getIpServerDependencies()));
         mTetherStates.put(iface, tetherState);
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 490614b..397ba8a 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -37,7 +37,6 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
-import android.net.ConnectivityManager;
 import android.net.TetheringConfigurationParcel;
 import android.net.util.SharedLog;
 import android.provider.Settings;
@@ -179,8 +178,8 @@
 
         pw.print("chooseUpstreamAutomatically: ");
         pw.println(chooseUpstreamAutomatically);
-        dumpStringArray(pw, "preferredUpstreamIfaceTypes",
-                preferredUpstreamNames(preferredUpstreamIfaceTypes));
+        pw.print("legacyPreredUpstreamIfaceTypes: ");
+        pw.println(Arrays.toString(toIntArray(preferredUpstreamIfaceTypes)));
 
         dumpStringArray(pw, "legacyDhcpRanges", legacyDhcpRanges);
         dumpStringArray(pw, "defaultIPv4DNS", defaultIPv4DNS);
@@ -205,7 +204,7 @@
         sj.add(String.format("isDunRequired:%s", isDunRequired));
         sj.add(String.format("chooseUpstreamAutomatically:%s", chooseUpstreamAutomatically));
         sj.add(String.format("preferredUpstreamIfaceTypes:%s",
-                makeString(preferredUpstreamNames(preferredUpstreamIfaceTypes))));
+                toIntArray(preferredUpstreamIfaceTypes)));
         sj.add(String.format("provisioningApp:%s", makeString(provisioningApp)));
         sj.add(String.format("provisioningAppNoUi:%s", provisioningAppNoUi));
         sj.add(String.format("enableLegacyDhcpServer:%s", enableLegacyDhcpServer));
@@ -234,21 +233,6 @@
         return sj.toString();
     }
 
-    private static String[] preferredUpstreamNames(Collection<Integer> upstreamTypes) {
-        String[] upstreamNames = null;
-
-        if (upstreamTypes != null) {
-            upstreamNames = new String[upstreamTypes.size()];
-            int i = 0;
-            for (Integer netType : upstreamTypes) {
-                upstreamNames[i] = ConnectivityManager.getNetworkTypeName(netType);
-                i++;
-            }
-        }
-
-        return upstreamNames;
-    }
-
     /** Check whether dun is required. */
     public static boolean checkDunRequired(Context ctx) {
         final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE);
@@ -388,6 +372,15 @@
         return false;
     }
 
+    private static int[] toIntArray(Collection<Integer> values) {
+        final int[] result = new int[values.size()];
+        int index = 0;
+        for (Integer value : values) {
+            result[index++] = value;
+        }
+        return result;
+    }
+
     /**
      * Convert this TetheringConfiguration to a TetheringConfigurationParcel.
      */
@@ -400,12 +393,7 @@
         parcel.isDunRequired = isDunRequired;
         parcel.chooseUpstreamAutomatically = chooseUpstreamAutomatically;
 
-        int[] preferredTypes = new int[preferredUpstreamIfaceTypes.size()];
-        int index = 0;
-        for (Integer type : preferredUpstreamIfaceTypes) {
-            preferredTypes[index++] = type;
-        }
-        parcel.preferredUpstreamIfaceTypes = preferredTypes;
+        parcel.preferredUpstreamIfaceTypes = toIntArray(preferredUpstreamIfaceTypes);
 
         parcel.legacyDhcpRanges = legacyDhcpRanges;
         parcel.defaultIPv4DNS = defaultIPv4DNS;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
index 6334c20..d5cdd8a 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
@@ -22,14 +22,17 @@
 import android.net.RouteInfo;
 import android.net.util.InterfaceSet;
 
-import java.net.Inet4Address;
-import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.net.UnknownHostException;
 
 /**
  * @hide
  */
 public final class TetheringInterfaceUtils {
+    private static final InetAddress IN6ADDR_ANY = getByAddress(
+            new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
+    private static final InetAddress INADDR_ANY = getByAddress(new byte[] {0, 0, 0, 0});
+
     /**
      * Get upstream interfaces for tethering based on default routes for IPv4/IPv6.
      * @return null if there is no usable interface, or a set of at least one interface otherwise.
@@ -40,7 +43,7 @@
         }
 
         final LinkProperties lp = ns.linkProperties;
-        final String if4 = getInterfaceForDestination(lp, Inet4Address.ANY);
+        final String if4 = getInterfaceForDestination(lp, INADDR_ANY);
         final String if6 = getIPv6Interface(ns);
 
         return (if4 == null && if6 == null) ? null : new InterfaceSet(if4, if6);
@@ -76,7 +79,7 @@
                 && ns.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
 
         return canTether
-                ? getInterfaceForDestination(ns.linkProperties, Inet6Address.ANY)
+                ? getInterfaceForDestination(ns.linkProperties, IN6ADDR_ANY)
                 : null;
     }
 
@@ -86,4 +89,12 @@
                 : null;
         return (ri != null) ? ri.getInterface() : null;
     }
+
+    private static InetAddress getByAddress(final byte[] addr) {
+        try {
+            return InetAddress.getByAddress(null, addr);
+        } catch (UnknownHostException e) {
+            throw new AssertionError("illegal address length" + addr.length);
+        }
+    }
 }
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
index 775484e..e4e4a09 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
@@ -26,11 +26,11 @@
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
-import android.net.ConnectivityManager;
 import android.net.IIntResultListener;
 import android.net.INetworkStackConnector;
 import android.net.ITetheringConnector;
 import android.net.ITetheringEventCallback;
+import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.dhcp.DhcpServerCallbacks;
 import android.net.dhcp.DhcpServingParamsParcel;
@@ -307,9 +307,15 @@
             mDeps = new TetheringDependencies() {
                 @Override
                 public NetworkRequest getDefaultNetworkRequest() {
-                    ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(
-                            Context.CONNECTIVITY_SERVICE);
-                    return cm.getDefaultRequest();
+                    // TODO: b/147280869, add a proper system API to replace this.
+                    final NetworkRequest trackDefaultRequest = new NetworkRequest.Builder()
+                            .clearCapabilities()
+                            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                            .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+                            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                            .build();
+                    return trackDefaultRequest;
                 }
 
                 @Override
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index 22150f6..2875f71 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -16,11 +16,15 @@
 
 package com.android.server.connectivity.tethering;
 
+import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
-import static android.net.ConnectivityManager.TYPE_NONE;
-import static android.net.ConnectivityManager.getNetworkTypeName;
+import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 
@@ -35,10 +39,11 @@
 import android.net.util.PrefixUtils;
 import android.net.util.SharedLog;
 import android.os.Handler;
-import android.os.Process;
 import android.util.Log;
+import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.StateMachine;
 
 import java.util.HashMap;
@@ -79,11 +84,25 @@
     public static final int EVENT_ON_LINKPROPERTIES = 2;
     public static final int EVENT_ON_LOST           = 3;
     public static final int NOTIFY_LOCAL_PREFIXES   = 10;
+    // This value is used by deprecated preferredUpstreamIfaceTypes selection which is default
+    // disabled.
+    @VisibleForTesting
+    public static final int TYPE_NONE = -1;
 
     private static final int CALLBACK_LISTEN_ALL = 1;
     private static final int CALLBACK_DEFAULT_INTERNET = 2;
     private static final int CALLBACK_MOBILE_REQUEST = 3;
 
+    private static final SparseIntArray sLegacyTypeToTransport = new SparseIntArray();
+    static {
+        sLegacyTypeToTransport.put(TYPE_MOBILE,       NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_DUN,   NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_HIPRI, NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_WIFI,         NetworkCapabilities.TRANSPORT_WIFI);
+        sLegacyTypeToTransport.put(TYPE_BLUETOOTH,    NetworkCapabilities.TRANSPORT_BLUETOOTH);
+        sLegacyTypeToTransport.put(TYPE_ETHERNET,     NetworkCapabilities.TRANSPORT_ETHERNET);
+    }
+
     private final Context mContext;
     private final SharedLog mLog;
     private final StateMachine mTarget;
@@ -130,15 +149,15 @@
      */
     public void startTrackDefaultNetwork(NetworkRequest defaultNetworkRequest,
             EntitlementManager entitle) {
-        // This is not really a "request", just a way of tracking the system default network.
-        // It's guaranteed not to actually bring up any networks because it's the same request
-        // as the ConnectivityService default request, and thus shares fate with it. We can't
-        // use registerDefaultNetworkCallback because it will not track the system default
-        // network if there is a VPN that applies to our UID.
+
+        // defaultNetworkRequest is not really a "request", just a way of tracking the system
+        // default network. It's guaranteed not to actually bring up any networks because it's
+        // the should be the same request as the ConnectivityService default request, and thus
+        // shares fate with it. We can't use registerDefaultNetworkCallback because it will not
+        // track the system default network if there is a VPN that applies to our UID.
         if (mDefaultNetworkCallback == null) {
-            final NetworkRequest trackDefaultRequest = new NetworkRequest(defaultNetworkRequest);
             mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET);
-            cm().requestNetwork(trackDefaultRequest, mDefaultNetworkCallback, mHandler);
+            cm().requestNetwork(defaultNetworkRequest, mDefaultNetworkCallback, mHandler);
         }
         if (mEntitlementMgr == null) {
             mEntitlementMgr = entitle;
@@ -198,19 +217,28 @@
             mLog.e("registerMobileNetworkRequest() already registered");
             return;
         }
-        // The following use of the legacy type system cannot be removed until
-        // after upstream selection no longer finds networks by legacy type.
-        // See also http://b/34364553 .
-        final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
 
-        final NetworkRequest mobileUpstreamRequest = new NetworkRequest.Builder()
-                .setCapabilities(ConnectivityManager.networkCapabilitiesForType(legacyType))
-                .build();
+        final NetworkRequest mobileUpstreamRequest;
+        if (mDunRequired) {
+            mobileUpstreamRequest = new NetworkRequest.Builder()
+                    .addCapability(NET_CAPABILITY_DUN)
+                    .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                    .addTransportType(TRANSPORT_CELLULAR).build();
+        } else {
+            mobileUpstreamRequest = new NetworkRequest.Builder()
+                    .addCapability(NET_CAPABILITY_INTERNET)
+                    .addTransportType(TRANSPORT_CELLULAR).build();
+        }
 
         // The existing default network and DUN callbacks will be notified.
         // Therefore, to avoid duplicate notifications, we only register a no-op.
         mMobileNetworkCallback = new UpstreamNetworkCallback(CALLBACK_MOBILE_REQUEST);
 
+        // The following use of the legacy type system cannot be removed until
+        // upstream selection no longer finds networks by legacy type.
+        // See also http://b/34364553 .
+        final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
+
         // TODO: Change the timeout from 0 (no onUnavailable callback) to some
         // moderate callback timeout. This might be useful for updating some UI.
         // Additionally, we log a message to aid in any subsequent debugging.
@@ -239,7 +267,7 @@
         final TypeStatePair typeStatePair = findFirstAvailableUpstreamByType(
                 mNetworkMap.values(), preferredTypes, isCellularUpstreamPermitted());
 
-        mLog.log("preferred upstream type: " + getNetworkTypeName(typeStatePair.type));
+        mLog.log("preferred upstream type: " + typeStatePair.type);
 
         switch (typeStatePair.type) {
             case TYPE_MOBILE_DUN:
@@ -356,16 +384,6 @@
         notifyTarget(EVENT_ON_LINKPROPERTIES, network);
     }
 
-    private void handleSuspended(Network network) {
-        if (!network.equals(mTetheringUpstreamNetwork)) return;
-        mLog.log("SUSPENDED current upstream: " + network);
-    }
-
-    private void handleResumed(Network network) {
-        if (!network.equals(mTetheringUpstreamNetwork)) return;
-        mLog.log("RESUMED current upstream: " + network);
-    }
-
     private void handleLost(Network network) {
         // There are few TODOs within ConnectivityService's rematching code
         // pertaining to spurious onLost() notifications.
@@ -455,20 +473,6 @@
         }
 
         @Override
-        public void onNetworkSuspended(Network network) {
-            if (mCallbackType == CALLBACK_LISTEN_ALL) {
-                handleSuspended(network);
-            }
-        }
-
-        @Override
-        public void onNetworkResumed(Network network) {
-            if (mCallbackType == CALLBACK_LISTEN_ALL) {
-                handleResumed(network);
-            }
-        }
-
-        @Override
         public void onLost(Network network) {
             if (mCallbackType == CALLBACK_DEFAULT_INTERNET) {
                 mDefaultInternetNetwork = null;
@@ -512,18 +516,15 @@
         for (int type : preferredTypes) {
             NetworkCapabilities nc;
             try {
-                nc = ConnectivityManager.networkCapabilitiesForType(type);
+                nc = networkCapabilitiesForType(type);
             } catch (IllegalArgumentException iae) {
-                Log.e(TAG, "No NetworkCapabilities mapping for legacy type: "
-                        + ConnectivityManager.getNetworkTypeName(type));
+                Log.e(TAG, "No NetworkCapabilities mapping for legacy type: " + type);
                 continue;
             }
             if (!isCellularUpstreamPermitted && isCellular(nc)) {
                 continue;
             }
 
-            nc.setSingleUid(Process.myUid());
-
             for (UpstreamNetworkState value : netStates) {
                 if (!nc.satisfiedByNetworkCapabilities(value.networkCapabilities)) {
                     continue;
@@ -577,4 +578,28 @@
 
         return null;
     }
+
+    /**
+     * Given a legacy type (TYPE_WIFI, ...) returns the corresponding NetworkCapabilities instance.
+     * This function is used for deprecated legacy type and be disabled by default.
+     */
+    @VisibleForTesting
+    public static NetworkCapabilities networkCapabilitiesForType(int type) {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+
+        // Map from type to transports.
+        final int notFound = -1;
+        final int transport = sLegacyTypeToTransport.get(type, notFound);
+        Preconditions.checkArgument(transport != notFound, "unknown legacy type: " + type);
+        nc.addTransportType(transport);
+
+        if (type == TYPE_MOBILE_DUN) {
+            nc.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
+            // DUN is restricted network, see NetworkCapabilities#FORCE_RESTRICTED_CAPABILITIES.
+            nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        } else {
+            nc.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        }
+        return nc;
+    }
 }
diff --git a/packages/Tethering/tests/unit/jarjar-rules.txt b/packages/Tethering/tests/unit/jarjar-rules.txt
index 64fdebd..921fbed 100644
--- a/packages/Tethering/tests/unit/jarjar-rules.txt
+++ b/packages/Tethering/tests/unit/jarjar-rules.txt
@@ -7,5 +7,6 @@
 rule com.android.internal.util.Preconditions* com.android.networkstack.tethering.util.Preconditions@1
 rule com.android.internal.util.State* com.android.networkstack.tethering.util.State@1
 rule com.android.internal.util.StateMachine* com.android.networkstack.tethering.util.StateMachine@1
+rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.tethering.util.TrafficStatsConstants@1
 
 rule android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1
diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index fd2f708..65a0ac1 100644
--- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -16,13 +16,14 @@
 
 package android.net.ip;
 
-import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
-import static android.net.ConnectivityManager.TETHERING_USB;
-import static android.net.ConnectivityManager.TETHERING_WIFI;
-import static android.net.ConnectivityManager.TETHERING_WIFI_P2P;
-import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.INetd.IF_STATE_UP;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.ip.IpServer.STATE_AVAILABLE;
 import static android.net.ip.IpServer.STATE_LOCAL_ONLY;
@@ -52,7 +53,7 @@
 
 import android.net.INetd;
 import android.net.INetworkStatsService;
-import android.net.InterfaceConfiguration;
+import android.net.InterfaceConfigurationParcel;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -64,7 +65,6 @@
 import android.net.util.InterfaceParams;
 import android.net.util.InterfaceSet;
 import android.net.util.SharedLog;
-import android.os.INetworkManagementService;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 import android.text.TextUtils;
@@ -89,6 +89,8 @@
     private static final String IFACE_NAME = "testnet1";
     private static final String UPSTREAM_IFACE = "upstream0";
     private static final String UPSTREAM_IFACE2 = "upstream1";
+    private static final String BLUETOOTH_IFACE_ADDR = "192.168.42.1";
+    private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24;
     private static final int DHCP_LEASE_TIME_SECS = 3600;
 
     private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams(
@@ -96,11 +98,9 @@
 
     private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000;
 
-    @Mock private INetworkManagementService mNMService;
     @Mock private INetd mNetd;
     @Mock private INetworkStatsService mStatsService;
     @Mock private IpServer.Callback mCallback;
-    @Mock private InterfaceConfiguration mInterfaceConfiguration;
     @Mock private SharedLog mSharedLog;
     @Mock private IDhcpServer mDhcpServer;
     @Mock private RouterAdvertisementDaemon mRaDaemon;
@@ -112,6 +112,7 @@
     private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor =
             ArgumentCaptor.forClass(LinkProperties.class);
     private IpServer mIpServer;
+    private InterfaceConfigurationParcel mInterfaceConfiguration;
 
     private void initStateMachine(int interfaceType) throws Exception {
         initStateMachine(interfaceType, false /* usingLegacyDhcp */);
@@ -131,17 +132,20 @@
         }).when(mDependencies).makeDhcpServer(any(), mDhcpParamsCaptor.capture(), any());
         when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon);
         when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS);
-        when(mDependencies.getNetdService()).thenReturn(mNetd);
-
+        mInterfaceConfiguration = new InterfaceConfigurationParcel();
+        mInterfaceConfiguration.flags = new String[0];
+        if (interfaceType == TETHERING_BLUETOOTH) {
+            mInterfaceConfiguration.ipv4Addr = BLUETOOTH_IFACE_ADDR;
+            mInterfaceConfiguration.prefixLength = BLUETOOTH_DHCP_PREFIX_LENGTH;
+        }
         mIpServer = new IpServer(
-                IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog,
-                mNMService, mStatsService, mCallback, usingLegacyDhcp, mDependencies);
+                IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mStatsService,
+                mCallback, usingLegacyDhcp, mDependencies);
         mIpServer.start();
         // Starting the state machine always puts us in a consistent state and notifies
         // the rest of the world that we've changed from an unknown to available state.
         mLooper.dispatchAll();
-        reset(mNMService, mStatsService, mCallback);
-        when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
+        reset(mNetd, mStatsService, mCallback);
 
         when(mRaDaemon.start()).thenReturn(true);
     }
@@ -158,8 +162,7 @@
         if (upstreamIface != null) {
             dispatchTetherConnectionChanged(upstreamIface);
         }
-        reset(mNMService, mStatsService, mCallback);
-        when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
+        reset(mNetd, mStatsService, mCallback);
     }
 
     @Before public void setUp() throws Exception {
@@ -169,15 +172,14 @@
 
     @Test
     public void startsOutAvailable() {
-        mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(),
-                TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mCallback,
-                false /* usingLegacyDhcp */, mDependencies);
+        mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
+                mNetd, mStatsService, mCallback, false /* usingLegacyDhcp */, mDependencies);
         mIpServer.start();
         mLooper.dispatchAll();
         verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
         verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(mCallback, mNMService, mStatsService);
+        verifyNoMoreInteractions(mCallback, mNetd, mStatsService);
     }
 
     @Test
@@ -196,7 +198,7 @@
             // None of these commands should trigger us to request action from
             // the rest of the system.
             dispatchCommand(command);
-            verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+            verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
         }
     }
 
@@ -208,7 +210,7 @@
         verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
         verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
     }
 
     @Test
@@ -216,13 +218,17 @@
         initStateMachine(TETHERING_BLUETOOTH);
 
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
-        InOrder inOrder = inOrder(mCallback, mNMService);
-        inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+        InOrder inOrder = inOrder(mCallback, mNetd);
+        inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
+        inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
+        // One for ipv4 route, one for ipv6 link local route.
+        inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
+                any(), any());
         inOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
     }
 
     @Test
@@ -230,14 +236,16 @@
         initTetheredStateMachine(TETHERING_BLUETOOTH, null);
 
         dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
-        InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback);
-        inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
+        InOrder inOrder = inOrder(mNetd, mStatsService, mCallback);
+        inOrder.verify(mNetd).tetherApplyDnsInterfaces();
+        inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
+        inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
         inOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
     }
 
     @Test
@@ -245,16 +253,19 @@
         initStateMachine(TETHERING_USB);
 
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
-        InOrder inOrder = inOrder(mCallback, mNMService);
-        inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
-        inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
-        inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+        InOrder inOrder = inOrder(mCallback, mNetd);
+        inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
+                  IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
+        inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
+        inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
+        inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
+                any(), any());
         inOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), mLinkPropertiesCaptor.capture());
         assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
     }
 
     @Test
@@ -262,16 +273,19 @@
         initStateMachine(TETHERING_WIFI_P2P);
 
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
-        InOrder inOrder = inOrder(mCallback, mNMService);
-        inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
-        inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
-        inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+        InOrder inOrder = inOrder(mCallback, mNetd);
+        inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
+                  IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
+        inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
+        inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
+        inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
+                any(), any());
         inOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), mLinkPropertiesCaptor.capture());
         assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
     }
 
     @Test
@@ -281,10 +295,10 @@
         // Telling the state machine about its upstream interface triggers
         // a little more configuration.
         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
-        InOrder inOrder = inOrder(mNMService);
-        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        InOrder inOrder = inOrder(mNetd);
+        inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
     }
 
     @Test
@@ -292,49 +306,49 @@
         initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
 
         dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
-        InOrder inOrder = inOrder(mNMService, mStatsService);
+        InOrder inOrder = inOrder(mNetd, mStatsService);
         inOrder.verify(mStatsService).forceUpdate();
-        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
-        inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
+        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
     }
 
     @Test
     public void handlesChangingUpstreamNatFailure() throws Exception {
         initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
 
-        doThrow(RemoteException.class).when(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
+        doThrow(RemoteException.class).when(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
 
         dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
-        InOrder inOrder = inOrder(mNMService, mStatsService);
+        InOrder inOrder = inOrder(mNetd, mStatsService);
         inOrder.verify(mStatsService).forceUpdate();
-        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
         inOrder.verify(mStatsService).forceUpdate();
-        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
-        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
     }
 
     @Test
     public void handlesChangingUpstreamInterfaceForwardingFailure() throws Exception {
         initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
 
-        doThrow(RemoteException.class).when(mNMService).startInterfaceForwarding(
+        doThrow(RemoteException.class).when(mNetd).ipfwdAddInterfaceForward(
                 IFACE_NAME, UPSTREAM_IFACE2);
 
         dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
-        InOrder inOrder = inOrder(mNMService, mStatsService);
+        InOrder inOrder = inOrder(mNetd, mStatsService);
         inOrder.verify(mStatsService).forceUpdate();
-        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
-        inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
         inOrder.verify(mStatsService).forceUpdate();
-        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
-        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
     }
 
     @Test
@@ -342,17 +356,19 @@
         initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
 
         dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
-        InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback);
+        InOrder inOrder = inOrder(mNetd, mStatsService, mCallback);
         inOrder.verify(mStatsService).forceUpdate();
-        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
-        inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
+        inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNetd).tetherApplyDnsInterfaces();
+        inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
+        inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
         inOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
     }
 
     @Test
@@ -361,13 +377,14 @@
             initTetheredStateMachine(TETHERING_USB, null);
 
             if (shouldThrow) {
-                doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME);
+                doThrow(RemoteException.class).when(mNetd).tetherInterfaceRemove(IFACE_NAME);
             }
             dispatchCommand(IpServer.CMD_INTERFACE_DOWN);
-            InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback);
-            usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
-            usbTeardownOrder.verify(mNMService).setInterfaceConfig(
-                    IFACE_NAME, mInterfaceConfiguration);
+            InOrder usbTeardownOrder = inOrder(mNetd, mCallback);
+            // Currently IpServer interfaceSetCfg twice to stop IPv4. One just set interface down
+            // Another one is set IPv4 to 0.0.0.0/0 as clearng ipv4 address.
+            usbTeardownOrder.verify(mNetd, times(2)).interfaceSetCfg(
+                    argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
             usbTeardownOrder.verify(mCallback).updateInterfaceState(
                     mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
             usbTeardownOrder.verify(mCallback).updateLinkProperties(
@@ -380,12 +397,15 @@
     public void usbShouldBeTornDownOnTetherError() throws Exception {
         initStateMachine(TETHERING_USB);
 
-        doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME);
+        doThrow(RemoteException.class).when(mNetd).tetherInterfaceAdd(IFACE_NAME);
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
-        InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback);
-        usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
-        usbTeardownOrder.verify(mNMService).setInterfaceConfig(
-                IFACE_NAME, mInterfaceConfiguration);
+        InOrder usbTeardownOrder = inOrder(mNetd, mCallback);
+        usbTeardownOrder.verify(mNetd).interfaceSetCfg(
+                argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
+        usbTeardownOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
+
+        usbTeardownOrder.verify(mNetd, times(2)).interfaceSetCfg(
+                argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
         usbTeardownOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR);
         usbTeardownOrder.verify(mCallback).updateLinkProperties(
@@ -397,11 +417,13 @@
     public void shouldTearDownUsbOnUpstreamError() throws Exception {
         initTetheredStateMachine(TETHERING_USB, null);
 
-        doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString());
+        doThrow(RemoteException.class).when(mNetd).tetherAddForward(anyString(), anyString());
         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
-        InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback);
-        usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
-        usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
+        InOrder usbTeardownOrder = inOrder(mNetd, mCallback);
+        usbTeardownOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE);
+
+        usbTeardownOrder.verify(mNetd, times(2)).interfaceSetCfg(
+                argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
         usbTeardownOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR);
         usbTeardownOrder.verify(mCallback).updateLinkProperties(
@@ -413,11 +435,11 @@
     public void ignoresDuplicateUpstreamNotifications() throws Exception {
         initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
 
-        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
 
         for (int i = 0; i < 5; i++) {
             dispatchTetherConnectionChanged(UPSTREAM_IFACE);
-            verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+            verifyNoMoreInteractions(mNetd, mStatsService, mCallback);
         }
     }
 
@@ -525,4 +547,12 @@
         // never see an empty interface name in any LinkProperties update.
         assertFalse(TextUtils.isEmpty(lp.getInterfaceName()));
     }
+
+    private boolean assertContainsFlag(String[] flags, String match) {
+        for (String flag : flags) {
+            if (flag.equals(match)) return true;
+        }
+        fail("Missing flag: " + match);
+        return false;
+    }
 }
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
index 99cf9e9..66eba9a 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
@@ -16,12 +16,12 @@
 
 package com.android.server.connectivity.tethering;
 
-import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
-import static android.net.ConnectivityManager.TETHERING_USB;
-import static android.net.ConnectivityManager.TETHERING_WIFI;
-import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN;
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKONWN;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index 04b2eb4..271769e 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -19,16 +19,15 @@
 import static android.hardware.usb.UsbManager.USB_CONFIGURED;
 import static android.hardware.usb.UsbManager.USB_CONNECTED;
 import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
-import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
-import static android.net.ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY;
-import static android.net.ConnectivityManager.EXTRA_ACTIVE_TETHER;
-import static android.net.ConnectivityManager.EXTRA_AVAILABLE_TETHER;
-import static android.net.ConnectivityManager.TETHERING_USB;
-import static android.net.ConnectivityManager.TETHERING_WIFI;
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
-import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
 import static android.net.RouteInfo.RTN_UNICAST;
+import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
+import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
+import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER;
+import static android.net.TetheringManager.EXTRA_AVAILABLE_TETHER;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
@@ -50,7 +49,6 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -74,14 +72,13 @@
 import android.net.INetworkStatsService;
 import android.net.ITetheringEventCallback;
 import android.net.InetAddresses;
-import android.net.InterfaceConfiguration;
+import android.net.InterfaceConfigurationParcel;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.MacAddress;
 import android.net.Network;
 import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
 import android.net.NetworkRequest;
 import android.net.RouteInfo;
 import android.net.TetherStatesParcel;
@@ -147,6 +144,7 @@
     private static final String TEST_USB_IFNAME = "test_rndis0";
     private static final String TEST_WLAN_IFNAME = "test_wlan0";
     private static final String TEST_P2P_IFNAME = "test_p2p-p2p0-0";
+    private static final String TETHERING_NAME = "Tethering";
 
     private static final int DHCPSERVER_START_TIMEOUT_MS = 1000;
 
@@ -185,6 +183,7 @@
     private BroadcastReceiver mBroadcastReceiver;
     private Tethering mTethering;
     private PhoneStateListener mPhoneStateListener;
+    private InterfaceConfigurationParcel mInterfaceConfiguration;
 
     private class TestContext extends BroadcastInterceptingContext {
         TestContext(Context base) {
@@ -225,6 +224,11 @@
             if (TelephonyManager.class.equals(serviceClass)) return Context.TELEPHONY_SERVICE;
             return super.getSystemServiceName(serviceClass);
         }
+
+        @Override
+        public Context createContextAsUser(UserHandle user, int flags) {
+            return mContext;
+        }
     }
 
     public class MockIpServerDependencies extends IpServer.Dependencies {
@@ -248,11 +252,6 @@
         }
 
         @Override
-        public INetd getNetdService() {
-            return mNetd;
-        }
-
-        @Override
         public void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
                 DhcpServerCallbacks cb) {
             new Thread(() -> {
@@ -429,15 +428,16 @@
                 .thenReturn(new int[0]);
         when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic))
                 .thenReturn(false);
-        when(mNMService.listInterfaces())
+        when(mNetd.interfaceGetList())
                 .thenReturn(new String[] {
                         TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME});
-        when(mNMService.getInterfaceConfig(anyString()))
-                .thenReturn(new InterfaceConfiguration());
+        mInterfaceConfiguration = new InterfaceConfigurationParcel();
+        mInterfaceConfiguration.flags = new String[0];
         when(mRouterAdvertisementDaemon.start())
                 .thenReturn(true);
 
         mServiceContext = new TestContext(mContext);
+        when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(null);
         mContentResolver = new MockContentResolver(mServiceContext);
         mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
@@ -495,15 +495,12 @@
         p2pInfo.groupFormed = isGroupFormed;
         p2pInfo.isGroupOwner = isGroupOwner;
 
-        NetworkInfo networkInfo = new NetworkInfo(TYPE_WIFI_P2P, 0, null, null);
-
         WifiP2pGroup group = new WifiP2pGroup();
         group.setIsGroupOwner(isGroupOwner);
         group.setInterface(ifname);
 
         final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
         intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, p2pInfo);
-        intent.putExtra(WifiP2pManager.EXTRA_NETWORK_INFO, networkInfo);
         intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, group);
         mServiceContext.sendBroadcastAsUserMultiplePermissions(intent, UserHandle.ALL,
                 P2P_RECEIVER_PERMISSIONS_FOR_BROADCAST);
@@ -523,10 +520,11 @@
     }
 
     private void verifyInterfaceServingModeStarted(String ifname) throws Exception {
-        verify(mNMService, times(1)).getInterfaceConfig(ifname);
-        verify(mNMService, times(1))
-                .setInterfaceConfig(eq(ifname), any(InterfaceConfiguration.class));
-        verify(mNMService, times(1)).tetherInterface(ifname);
+        verify(mNetd, times(1)).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+        verify(mNetd, times(1)).tetherInterfaceAdd(ifname);
+        verify(mNetd, times(1)).networkAddInterface(INetd.LOCAL_NET_ID, ifname);
+        verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(ifname),
+                anyString(), anyString());
     }
 
     private void verifyTetheringBroadcast(String ifname, String whichExtra) {
@@ -558,7 +556,7 @@
             verify(mWifiManager).updateInterfaceIpState(
                     TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
         }
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
         verifyNoMoreInteractions(mWifiManager);
     }
 
@@ -581,14 +579,14 @@
         prepareUsbTethering(upstreamState);
 
         // This should produce no activity of any kind.
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
 
         // Pretend we then receive USB configured broadcast.
         sendUsbBroadcast(true, true, true);
         mLooper.dispatchAll();
         // Now we should see the start of tethering mechanics (in this case:
         // tetherMatchingInterfaces() which starts by fetching all interfaces).
-        verify(mNMService, times(1)).listInterfaces();
+        verify(mNetd, times(1)).interfaceGetList();
 
         // UpstreamNetworkMonitor should receive selected upstream
         verify(mUpstreamNetworkMonitor, times(1)).selectPreferredUpstreamType(any());
@@ -618,9 +616,9 @@
 
         verifyInterfaceServingModeStarted(TEST_WLAN_IFNAME);
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
-        verify(mNMService, times(1)).setIpForwardingEnabled(true);
-        verify(mNMService, times(1)).startTethering(any(String[].class));
-        verifyNoMoreInteractions(mNMService);
+        verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME);
+        verify(mNetd, times(1)).tetherStartWithConfiguration(any());
+        verifyNoMoreInteractions(mNetd);
         verify(mWifiManager).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
         verify(mWifiManager).updateInterfaceIpState(
@@ -638,16 +636,16 @@
         mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
         mLooper.dispatchAll();
 
-        verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME);
-        // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4.
-        verify(mNMService, times(2)).getInterfaceConfig(TEST_WLAN_IFNAME);
-        verify(mNMService, times(2))
-                .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
-        verify(mNMService, times(1)).stopTethering();
-        verify(mNMService, times(1)).setIpForwardingEnabled(false);
+        verify(mNetd, times(1)).tetherApplyDnsInterfaces();
+        verify(mNetd, times(1)).tetherInterfaceRemove(TEST_WLAN_IFNAME);
+        verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
+        // interfaceSetCfg() called once for enabling and twice disabling IPv4.
+        verify(mNetd, times(3)).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+        verify(mNetd, times(1)).tetherStop();
+        verify(mNetd, times(1)).ipfwdDisableForwarding(TETHERING_NAME);
         verify(mWifiManager, times(3)).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
         verifyNoMoreInteractions(mWifiManager);
         // Asking for the last error after the per-interface state machine
         // has been reaped yields an unknown interface error.
@@ -684,8 +682,8 @@
         UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
         runUsbTethering(upstreamState);
 
-        verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
-        verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
 
         sendIPv6TetherUpdates(upstreamState);
         verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull());
@@ -708,8 +706,8 @@
         UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState();
         runUsbTethering(upstreamState);
 
-        verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
-        verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
 
         sendIPv6TetherUpdates(upstreamState);
         verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
@@ -721,8 +719,8 @@
         UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
         runUsbTethering(upstreamState);
 
-        verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
-        verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
         verify(mRouterAdvertisementDaemon, times(1)).start();
         verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
 
@@ -736,12 +734,11 @@
         UpstreamNetworkState upstreamState = buildMobile464xlatUpstreamState();
         runUsbTethering(upstreamState);
 
-        verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
-        verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
+        verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
         verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
-        verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
-        verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME,
-                TEST_XLAT_MOBILE_IFNAME);
+        verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
+        verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
 
         sendIPv6TetherUpdates(upstreamState);
         verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
@@ -754,9 +751,9 @@
         UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState();
         runUsbTethering(upstreamState);
 
-        verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
         verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
-        verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
 
         // Then 464xlat comes up
         upstreamState = buildMobile464xlatUpstreamState();
@@ -772,12 +769,11 @@
         mLooper.dispatchAll();
 
         // Forwarding is added for 464xlat
-        verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
-        verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME,
-                TEST_XLAT_MOBILE_IFNAME);
+        verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
+        verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
         // Forwarding was not re-added for v6 (still times(1))
-        verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
-        verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
         // DHCP not restarted on downstream (still times(1))
         verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
     }
@@ -820,7 +816,7 @@
         mLooper.dispatchAll();
         verify(mWifiManager, times(1)).startTetheredHotspot(null);
         verifyNoMoreInteractions(mWifiManager);
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
 
         // Emulate externally-visible WifiManager effects, causing the
         // per-interface state machine to start up, and telling us that
@@ -833,7 +829,7 @@
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
         verify(mWifiManager).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
         verifyNoMoreInteractions(mWifiManager);
     }
 
@@ -847,7 +843,7 @@
         mLooper.dispatchAll();
         verify(mWifiManager, times(1)).startTetheredHotspot(null);
         verifyNoMoreInteractions(mWifiManager);
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
 
         // Emulate externally-visible WifiManager effects, causing the
         // per-interface state machine to start up, and telling us that
@@ -858,9 +854,11 @@
 
         verifyInterfaceServingModeStarted(TEST_WLAN_IFNAME);
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
-        verify(mNMService, times(1)).setIpForwardingEnabled(true);
-        verify(mNMService, times(1)).startTethering(any(String[].class));
-        verifyNoMoreInteractions(mNMService);
+        verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME);
+        verify(mNetd, times(1)).tetherStartWithConfiguration(any());
+        verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_WLAN_IFNAME),
+                anyString(), anyString());
+        verifyNoMoreInteractions(mNetd);
         verify(mWifiManager).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
         verify(mWifiManager).updateInterfaceIpState(
@@ -878,8 +876,8 @@
         /////
         // We do not currently emulate any upstream being found.
         //
-        // This is why there are no calls to verify mNMService.enableNat() or
-        // mNMService.startInterfaceForwarding().
+        // This is why there are no calls to verify mNetd.tetherAddForward() or
+        // mNetd.ipfwdAddInterfaceForward().
         /////
 
         // Emulate pressing the WiFi tethering button.
@@ -887,7 +885,7 @@
         mLooper.dispatchAll();
         verify(mWifiManager, times(1)).stopSoftAp();
         verifyNoMoreInteractions(mWifiManager);
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
 
         // Emulate externally-visible WifiManager effects, when tethering mode
         // is being torn down.
@@ -895,16 +893,16 @@
         mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
         mLooper.dispatchAll();
 
-        verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME);
-        // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4.
-        verify(mNMService, atLeastOnce()).getInterfaceConfig(TEST_WLAN_IFNAME);
-        verify(mNMService, atLeastOnce())
-                .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
-        verify(mNMService, times(1)).stopTethering();
-        verify(mNMService, times(1)).setIpForwardingEnabled(false);
+        verify(mNetd, times(1)).tetherApplyDnsInterfaces();
+        verify(mNetd, times(1)).tetherInterfaceRemove(TEST_WLAN_IFNAME);
+        verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
+        // interfaceSetCfg() called once for enabling and twice for disabling IPv4.
+        verify(mNetd, times(3)).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+        verify(mNetd, times(1)).tetherStop();
+        verify(mNetd, times(1)).ipfwdDisableForwarding(TETHERING_NAME);
         verify(mWifiManager, times(3)).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
         verifyNoMoreInteractions(mWifiManager);
         // Asking for the last error after the per-interface state machine
         // has been reaped yields an unknown interface error.
@@ -915,14 +913,14 @@
     @Test
     public void failureEnablingIpForwarding() throws Exception {
         when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
-        doThrow(new RemoteException()).when(mNMService).setIpForwardingEnabled(true);
+        doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME);
 
         // Emulate pressing the WiFi tethering button.
         mTethering.startTethering(TETHERING_WIFI, null, false);
         mLooper.dispatchAll();
         verify(mWifiManager, times(1)).startTetheredHotspot(null);
         verifyNoMoreInteractions(mWifiManager);
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
 
         // Emulate externally-visible WifiManager effects, causing the
         // per-interface state machine to start up, and telling us that
@@ -931,15 +929,15 @@
         sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
         mLooper.dispatchAll();
 
-        // We verify get/set called thrice here: twice for setup (on NMService) and once during
-        // teardown (on Netd) because all events happen over the course of the single
+        // We verify get/set called three times here: twice for setup and once during
+        // teardown because all events happen over the course of the single
         // dispatchAll() above. Note that once the IpServer IPv4 address config
         // code is refactored the two calls during shutdown will revert to one.
-        verify(mNMService, times(2)).getInterfaceConfig(TEST_WLAN_IFNAME);
-        verify(mNMService, times(2))
-                .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
-        verify(mNetd, times(1)).interfaceSetCfg(argThat(p -> TEST_WLAN_IFNAME.equals(p.ifName)));
-        verify(mNMService, times(1)).tetherInterface(TEST_WLAN_IFNAME);
+        verify(mNetd, times(3)).interfaceSetCfg(argThat(p -> TEST_WLAN_IFNAME.equals(p.ifName)));
+        verify(mNetd, times(1)).tetherInterfaceAdd(TEST_WLAN_IFNAME);
+        verify(mNetd, times(1)).networkAddInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
+        verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_WLAN_IFNAME),
+                anyString(), anyString());
         verify(mWifiManager).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
         verify(mWifiManager).updateInterfaceIpState(
@@ -949,18 +947,20 @@
         assertEquals(3, mTetheringDependencies.mIsTetheringSupportedCalls);
         verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
         // This is called, but will throw.
-        verify(mNMService, times(1)).setIpForwardingEnabled(true);
+        verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME);
         // This never gets called because of the exception thrown above.
-        verify(mNMService, times(0)).startTethering(any(String[].class));
+        verify(mNetd, times(0)).tetherStartWithConfiguration(any());
         // When the master state machine transitions to an error state it tells
         // downstream interfaces, which causes us to tell Wi-Fi about the error
         // so it can take down AP mode.
-        verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME);
+        verify(mNetd, times(1)).tetherApplyDnsInterfaces();
+        verify(mNetd, times(1)).tetherInterfaceRemove(TEST_WLAN_IFNAME);
+        verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
         verify(mWifiManager).updateInterfaceIpState(
                 TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
 
         verifyNoMoreInteractions(mWifiManager);
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
     }
 
     private void runUserRestrictionsChange(
@@ -1232,9 +1232,9 @@
 
         verifyInterfaceServingModeStarted(TEST_P2P_IFNAME);
         verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_AVAILABLE_TETHER);
-        verify(mNMService, times(1)).setIpForwardingEnabled(true);
-        verify(mNMService, times(1)).startTethering(any(String[].class));
-        verifyNoMoreInteractions(mNMService);
+        verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME);
+        verify(mNetd, times(1)).tetherStartWithConfiguration(any());
+        verifyNoMoreInteractions(mNetd);
         verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
         verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
         // This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
@@ -1249,16 +1249,16 @@
         mTethering.interfaceRemoved(TEST_P2P_IFNAME);
         mLooper.dispatchAll();
 
-        verify(mNMService, times(1)).untetherInterface(TEST_P2P_IFNAME);
-        // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4.
-        verify(mNMService, times(2)).getInterfaceConfig(TEST_P2P_IFNAME);
-        verify(mNMService, times(2))
-                .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class));
-        verify(mNMService, times(1)).stopTethering();
-        verify(mNMService, times(1)).setIpForwardingEnabled(false);
+        verify(mNetd, times(1)).tetherApplyDnsInterfaces();
+        verify(mNetd, times(1)).tetherInterfaceRemove(TEST_P2P_IFNAME);
+        verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_P2P_IFNAME);
+        // interfaceSetCfg() called once for enabling and twice for disabling IPv4.
+        verify(mNetd, times(3)).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+        verify(mNetd, times(1)).tetherStop();
+        verify(mNetd, times(1)).ipfwdDisableForwarding(TETHERING_NAME);
         verify(mUpstreamNetworkMonitor, never()).getCurrentPreferredUpstream();
         verify(mUpstreamNetworkMonitor, never()).selectPreferredUpstreamType(any());
-        verifyNoMoreInteractions(mNMService);
+        verifyNoMoreInteractions(mNetd);
         // Asking for the last error after the per-interface state machine
         // has been reaped yields an unknown interface error.
         assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME));
@@ -1272,12 +1272,11 @@
         sendWifiP2pConnectionChanged(true, false, TEST_P2P_IFNAME);
         mLooper.dispatchAll();
 
-        verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME);
-        verify(mNMService, never())
-                .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class));
-        verify(mNMService, never()).tetherInterface(TEST_P2P_IFNAME);
-        verify(mNMService, never()).setIpForwardingEnabled(true);
-        verify(mNMService, never()).startTethering(any(String[].class));
+        verify(mNetd, never()).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+        verify(mNetd, never()).tetherInterfaceAdd(TEST_P2P_IFNAME);
+        verify(mNetd, never()).networkAddInterface(INetd.LOCAL_NET_ID, TEST_P2P_IFNAME);
+        verify(mNetd, never()).ipfwdEnableForwarding(TETHERING_NAME);
+        verify(mNetd, never()).tetherStartWithConfiguration(any());
 
         // Emulate externally-visible WifiP2pManager effects, when wifi p2p group
         // is being removed.
@@ -1285,13 +1284,13 @@
         mTethering.interfaceRemoved(TEST_P2P_IFNAME);
         mLooper.dispatchAll();
 
-        verify(mNMService, never()).untetherInterface(TEST_P2P_IFNAME);
-        verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME);
-        verify(mNMService, never())
-                .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class));
-        verify(mNMService, never()).stopTethering();
-        verify(mNMService, never()).setIpForwardingEnabled(false);
-        verifyNoMoreInteractions(mNMService);
+        verify(mNetd, never()).tetherApplyDnsInterfaces();
+        verify(mNetd, never()).tetherInterfaceRemove(TEST_P2P_IFNAME);
+        verify(mNetd, never()).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_P2P_IFNAME);
+        verify(mNetd, never()).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+        verify(mNetd, never()).tetherStop();
+        verify(mNetd, never()).ipfwdDisableForwarding(TETHERING_NAME);
+        verifyNoMoreInteractions(mNetd);
         // Asking for the last error after the per-interface state machine
         // has been reaped yields an unknown interface error.
         assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME));
@@ -1321,12 +1320,11 @@
         sendWifiP2pConnectionChanged(true, true, TEST_P2P_IFNAME);
         mLooper.dispatchAll();
 
-        verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME);
-        verify(mNMService, never())
-                .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class));
-        verify(mNMService, never()).tetherInterface(TEST_P2P_IFNAME);
-        verify(mNMService, never()).setIpForwardingEnabled(true);
-        verify(mNMService, never()).startTethering(any(String[].class));
+        verify(mNetd, never()).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+        verify(mNetd, never()).tetherInterfaceAdd(TEST_P2P_IFNAME);
+        verify(mNetd, never()).networkAddInterface(INetd.LOCAL_NET_ID, TEST_P2P_IFNAME);
+        verify(mNetd, never()).ipfwdEnableForwarding(TETHERING_NAME);
+        verify(mNetd, never()).tetherStartWithConfiguration(any());
         assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME));
     }
     @Test
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index c90abbb..5ed75bf 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -18,13 +18,14 @@
 
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
-import static android.net.ConnectivityManager.TYPE_NONE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
+import static com.android.server.connectivity.tethering.UpstreamNetworkMonitor.TYPE_NONE;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -538,13 +539,15 @@
                 mUNM.selectPreferredUpstreamType(preferredTypes));
         verify(mEntitleMgr, times(1)).maybeRunProvisioning();
     }
+
     private void assertSatisfiesLegacyType(int legacyType, UpstreamNetworkState ns) {
         if (legacyType == TYPE_NONE) {
             assertTrue(ns == null);
             return;
         }
 
-        final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType);
+        final NetworkCapabilities nc =
+                UpstreamNetworkMonitor.networkCapabilitiesForType(legacyType);
         assertTrue(nc.satisfiedByNetworkCapabilities(ns.networkCapabilities));
     }
 
diff --git a/packages/services/PacProcessor/jni/Android.bp b/packages/services/PacProcessor/jni/Android.bp
index 2a94237..61f8143 100644
--- a/packages/services/PacProcessor/jni/Android.bp
+++ b/packages/services/PacProcessor/jni/Android.bp
@@ -37,4 +37,10 @@
         "-Wunused",
         "-Wunreachable-code",
     ],
+    sanitize: {
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
 }
diff --git a/rs/java/android/renderscript/BaseObj.java b/rs/java/android/renderscript/BaseObj.java
index b7e05d9..7b5514b 100644
--- a/rs/java/android/renderscript/BaseObj.java
+++ b/rs/java/android/renderscript/BaseObj.java
@@ -16,8 +16,10 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
 import dalvik.system.CloseGuard;
+
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 /**
diff --git a/rs/java/android/renderscript/Element.java b/rs/java/android/renderscript/Element.java
index b8eb3a1..0941907 100644
--- a/rs/java/android/renderscript/Element.java
+++ b/rs/java/android/renderscript/Element.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * <p>An Element represents one item within an {@link
diff --git a/rs/java/android/renderscript/FileA3D.java b/rs/java/android/renderscript/FileA3D.java
index 9a6b0bc..7cc2825 100644
--- a/rs/java/android/renderscript/FileA3D.java
+++ b/rs/java/android/renderscript/FileA3D.java
@@ -16,13 +16,13 @@
 
 package android.renderscript;
 
-import java.io.File;
-import java.io.InputStream;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 
+import java.io.File;
+import java.io.InputStream;
+
 /**
  * @hide
  * @deprecated in API 16
diff --git a/rs/java/android/renderscript/Font.java b/rs/java/android/renderscript/Font.java
index 583350e..df9d801 100644
--- a/rs/java/android/renderscript/Font.java
+++ b/rs/java/android/renderscript/Font.java
@@ -16,17 +16,16 @@
 
 package android.renderscript;
 
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.Environment;
+
 import java.io.File;
 import java.io.InputStream;
 import java.util.HashMap;
 import java.util.Map;
 
-import android.os.Environment;
-
-import android.annotation.UnsupportedAppUsage;
-import android.content.res.AssetManager;
-import android.content.res.Resources;
-
 /**
  * @hide
  * @deprecated in API 16
diff --git a/rs/java/android/renderscript/Matrix4f.java b/rs/java/android/renderscript/Matrix4f.java
index 026c9fb..a9469c9 100644
--- a/rs/java/android/renderscript/Matrix4f.java
+++ b/rs/java/android/renderscript/Matrix4f.java
@@ -16,8 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
-import java.lang.Math;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 /**
diff --git a/rs/java/android/renderscript/Mesh.java b/rs/java/android/renderscript/Mesh.java
index 5321dcb..826225a 100644
--- a/rs/java/android/renderscript/Mesh.java
+++ b/rs/java/android/renderscript/Mesh.java
@@ -16,7 +16,8 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
 import java.util.Vector;
 
 /**
diff --git a/rs/java/android/renderscript/Program.java b/rs/java/android/renderscript/Program.java
index e28d646..ff07218 100644
--- a/rs/java/android/renderscript/Program.java
+++ b/rs/java/android/renderscript/Program.java
@@ -17,14 +17,14 @@
 package android.renderscript;
 
 
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.Resources;
+import android.util.Log;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
 
-import android.annotation.UnsupportedAppUsage;
-import android.content.res.Resources;
-import android.util.Log;
-
 
 /**
  * @hide
diff --git a/rs/java/android/renderscript/ProgramFragment.java b/rs/java/android/renderscript/ProgramFragment.java
index 3dde9b6..8805312 100644
--- a/rs/java/android/renderscript/ProgramFragment.java
+++ b/rs/java/android/renderscript/ProgramFragment.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 /**
diff --git a/rs/java/android/renderscript/ProgramFragmentFixedFunction.java b/rs/java/android/renderscript/ProgramFragmentFixedFunction.java
index d05d41d..c741ce6 100644
--- a/rs/java/android/renderscript/ProgramFragmentFixedFunction.java
+++ b/rs/java/android/renderscript/ProgramFragmentFixedFunction.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 /**
diff --git a/rs/java/android/renderscript/ProgramRaster.java b/rs/java/android/renderscript/ProgramRaster.java
index 33000ac..a21696c 100644
--- a/rs/java/android/renderscript/ProgramRaster.java
+++ b/rs/java/android/renderscript/ProgramRaster.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 /**
diff --git a/rs/java/android/renderscript/ProgramStore.java b/rs/java/android/renderscript/ProgramStore.java
index 622fe21..7e61347 100644
--- a/rs/java/android/renderscript/ProgramStore.java
+++ b/rs/java/android/renderscript/ProgramStore.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 /**
diff --git a/rs/java/android/renderscript/ProgramVertex.java b/rs/java/android/renderscript/ProgramVertex.java
index 83d9ea7..9257234 100644
--- a/rs/java/android/renderscript/ProgramVertex.java
+++ b/rs/java/android/renderscript/ProgramVertex.java
@@ -38,7 +38,7 @@
  **/
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 /**
diff --git a/rs/java/android/renderscript/ProgramVertexFixedFunction.java b/rs/java/android/renderscript/ProgramVertexFixedFunction.java
index 579d3bb..03c2eaf 100644
--- a/rs/java/android/renderscript/ProgramVertexFixedFunction.java
+++ b/rs/java/android/renderscript/ProgramVertexFixedFunction.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 /**
diff --git a/rs/java/android/renderscript/RSSurfaceView.java b/rs/java/android/renderscript/RSSurfaceView.java
index 561373c..6bdde38 100644
--- a/rs/java/android/renderscript/RSSurfaceView.java
+++ b/rs/java/android/renderscript/RSSurfaceView.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.SurfaceHolder;
diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java
index f4c2777..39efe73 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.AssetManager;
 import android.graphics.Bitmap;
@@ -447,44 +447,33 @@
         validate();
         return rsnAllocationCreateTyped(mContext, type, mip, usage, pointer);
     }
-    native long rsnAllocationCreateFromBitmap(long con, long type, int mip, long bitmapHandle,
+
+    native long rsnAllocationCreateFromBitmap(long con, long type, int mip, Bitmap bmp,
                 int usage);
     synchronized long nAllocationCreateFromBitmap(long type, int mip, Bitmap bmp, int usage) {
         validate();
-        return rsnAllocationCreateFromBitmap(mContext, type, mip, bmp.getNativeInstance(), usage);
+        return rsnAllocationCreateFromBitmap(mContext, type, mip, bmp, usage);
     }
 
-    native long rsnAllocationCreateBitmapBackedAllocation(long con, long type, int mip, long bitmapHandle,
+    native long rsnAllocationCreateBitmapBackedAllocation(long con, long type, int mip, Bitmap bmp,
                 int usage);
     synchronized long nAllocationCreateBitmapBackedAllocation(long type, int mip, Bitmap bmp,
                 int usage) {
         validate();
-        return rsnAllocationCreateBitmapBackedAllocation(mContext, type, mip, bmp.getNativeInstance(),
-                usage);
+        return rsnAllocationCreateBitmapBackedAllocation(mContext, type, mip, bmp, usage);
     }
 
-    native long rsnAllocationCubeCreateFromBitmap(long con, long type, int mip, long bitmapHandle,
+    native long rsnAllocationCubeCreateFromBitmap(long con, long type, int mip, Bitmap bmp,
                 int usage);
     synchronized long nAllocationCubeCreateFromBitmap(long type, int mip, Bitmap bmp, int usage) {
         validate();
-        return rsnAllocationCubeCreateFromBitmap(mContext, type, mip, bmp.getNativeInstance(),
-                usage);
-    }
-    native long  rsnAllocationCreateBitmapRef(long con, long type, long bitmapHandle);
-    synchronized long nAllocationCreateBitmapRef(long type, Bitmap bmp) {
-        validate();
-        return rsnAllocationCreateBitmapRef(mContext, type, bmp.getNativeInstance());
-    }
-    native long  rsnAllocationCreateFromAssetStream(long con, int mips, int assetStream, int usage);
-    synchronized long nAllocationCreateFromAssetStream(int mips, int assetStream, int usage) {
-        validate();
-        return rsnAllocationCreateFromAssetStream(mContext, mips, assetStream, usage);
+        return rsnAllocationCubeCreateFromBitmap(mContext, type, mip, bmp, usage);
     }
 
-    native void  rsnAllocationCopyToBitmap(long con, long alloc, long bitmapHandle);
+    native void  rsnAllocationCopyToBitmap(long con, long alloc, Bitmap bmp);
     synchronized void nAllocationCopyToBitmap(long alloc, Bitmap bmp) {
         validate();
-        rsnAllocationCopyToBitmap(mContext, alloc, bmp.getNativeInstance());
+        rsnAllocationCopyToBitmap(mContext, alloc, bmp);
     }
 
     native void rsnAllocationSyncAll(long con, long alloc, int src);
@@ -537,10 +526,10 @@
         validate();
         rsnAllocationGenerateMipmaps(mContext, alloc);
     }
-    native void  rsnAllocationCopyFromBitmap(long con, long alloc, long bitmapHandle);
+    native void  rsnAllocationCopyFromBitmap(long con, long alloc, Bitmap bmp);
     synchronized void nAllocationCopyFromBitmap(long alloc, Bitmap bmp) {
         validate();
-        rsnAllocationCopyFromBitmap(mContext, alloc, bmp.getNativeInstance());
+        rsnAllocationCopyFromBitmap(mContext, alloc, bmp);
     }
 
 
diff --git a/rs/java/android/renderscript/RenderScriptCacheDir.java b/rs/java/android/renderscript/RenderScriptCacheDir.java
index 1797bef..862d032 100644
--- a/rs/java/android/renderscript/RenderScriptCacheDir.java
+++ b/rs/java/android/renderscript/RenderScriptCacheDir.java
@@ -16,7 +16,8 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
 import java.io.File;
 
 /**
diff --git a/rs/java/android/renderscript/RenderScriptGL.java b/rs/java/android/renderscript/RenderScriptGL.java
index 6fac83e..dafaf36 100644
--- a/rs/java/android/renderscript/RenderScriptGL.java
+++ b/rs/java/android/renderscript/RenderScriptGL.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.graphics.SurfaceTexture;
 import android.view.Surface;
diff --git a/rs/java/android/renderscript/Script.java b/rs/java/android/renderscript/Script.java
index 9ad9aea..d1d3a76 100644
--- a/rs/java/android/renderscript/Script.java
+++ b/rs/java/android/renderscript/Script.java
@@ -16,7 +16,7 @@
 
 package android.renderscript;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.util.SparseArray;
 
 /**
diff --git a/rs/jni/Android.mk b/rs/jni/Android.mk
index 0854b95..f9ef0b7 100644
--- a/rs/jni/Android.mk
+++ b/rs/jni/Android.mk
@@ -12,7 +12,6 @@
     libRS \
     libcutils \
     liblog \
-    libhwui \
     libutils \
     libui \
     libgui \
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index dfee961..5ae895d 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -32,10 +32,10 @@
 
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
+#include <android/graphics/bitmap.h>
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/android_view_Surface.h"
 #include "android_runtime/android_util_AssetManager.h"
-#include "android/graphics/GraphicsJNI.h"
 #include "android/native_window.h"
 #include "android/native_window_jni.h"
 
@@ -1319,27 +1319,28 @@
     rsAllocationGenerateMipmaps((RsContext)con, (RsAllocation)alloc);
 }
 
+static size_t computeByteSize(const android::graphics::Bitmap& bitmap) {
+    AndroidBitmapInfo info = bitmap.getInfo();
+    return info.height * info.stride;
+}
+
 static jlong
 nAllocationCreateFromBitmap(JNIEnv *_env, jobject _this, jlong con, jlong type, jint mip,
-                            jlong bitmapPtr, jint usage)
+                            jobject jbitmap, jint usage)
 {
-    SkBitmap bitmap;
-    bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
-
+    android::graphics::Bitmap bitmap(_env, jbitmap);
     const void* ptr = bitmap.getPixels();
     jlong id = (jlong)(uintptr_t)rsAllocationCreateFromBitmap((RsContext)con,
                                                   (RsType)type, (RsAllocationMipmapControl)mip,
-                                                  ptr, bitmap.computeByteSize(), usage);
+                                                  ptr, computeByteSize(bitmap), usage);
     return id;
 }
 
 static jlong
 nAllocationCreateBitmapBackedAllocation(JNIEnv *_env, jobject _this, jlong con, jlong type,
-                                        jint mip, jlong bitmapPtr, jint usage)
+                                        jint mip, jobject jbitmap, jint usage)
 {
-    SkBitmap bitmap;
-    bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
-
+    android::graphics::Bitmap bitmap(_env, jbitmap);
     const void* ptr = bitmap.getPixels();
     jlong id = (jlong)(uintptr_t)rsAllocationCreateTyped((RsContext)con,
                                             (RsType)type, (RsAllocationMipmapControl)mip,
@@ -1349,40 +1350,35 @@
 
 static jlong
 nAllocationCubeCreateFromBitmap(JNIEnv *_env, jobject _this, jlong con, jlong type, jint mip,
-                                jlong bitmapPtr, jint usage)
+                                jobject jbitmap, jint usage)
 {
-    SkBitmap bitmap;
-    bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
-
+    android::graphics::Bitmap bitmap(_env, jbitmap);
     const void* ptr = bitmap.getPixels();
     jlong id = (jlong)(uintptr_t)rsAllocationCubeCreateFromBitmap((RsContext)con,
                                                       (RsType)type, (RsAllocationMipmapControl)mip,
-                                                      ptr, bitmap.computeByteSize(), usage);
+                                                      ptr, computeByteSize(bitmap), usage);
     return id;
 }
 
 static void
-nAllocationCopyFromBitmap(JNIEnv *_env, jobject _this, jlong con, jlong alloc, jlong bitmapPtr)
+nAllocationCopyFromBitmap(JNIEnv *_env, jobject _this, jlong con, jlong alloc, jobject jbitmap)
 {
-    SkBitmap bitmap;
-    bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
-    int w = bitmap.width();
-    int h = bitmap.height();
+    android::graphics::Bitmap bitmap(_env, jbitmap);
+    int w = bitmap.getInfo().width;
+    int h = bitmap.getInfo().height;
 
     const void* ptr = bitmap.getPixels();
     rsAllocation2DData((RsContext)con, (RsAllocation)alloc, 0, 0,
                        0, RS_ALLOCATION_CUBEMAP_FACE_POSITIVE_X,
-                       w, h, ptr, bitmap.computeByteSize(), 0);
+                       w, h, ptr, computeByteSize(bitmap), 0);
 }
 
 static void
-nAllocationCopyToBitmap(JNIEnv *_env, jobject _this, jlong con, jlong alloc, jlong bitmapPtr)
+nAllocationCopyToBitmap(JNIEnv *_env, jobject _this, jlong con, jlong alloc, jobject jbitmap)
 {
-    SkBitmap bitmap;
-    bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
-
+    android::graphics::Bitmap bitmap(_env, jbitmap);
     void* ptr = bitmap.getPixels();
-    rsAllocationCopyToBitmap((RsContext)con, (RsAllocation)alloc, ptr, bitmap.computeByteSize());
+    rsAllocationCopyToBitmap((RsContext)con, (RsAllocation)alloc, ptr, computeByteSize(bitmap));
     bitmap.notifyPixelsChanged();
 }
 
@@ -2867,12 +2863,12 @@
 {"rsnTypeGetNativeData",             "(JJ[J)V",                               (void*)nTypeGetNativeData },
 
 {"rsnAllocationCreateTyped",         "(JJIIJ)J",                              (void*)nAllocationCreateTyped },
-{"rsnAllocationCreateFromBitmap",    "(JJIJI)J",                              (void*)nAllocationCreateFromBitmap },
-{"rsnAllocationCreateBitmapBackedAllocation",    "(JJIJI)J",                  (void*)nAllocationCreateBitmapBackedAllocation },
-{"rsnAllocationCubeCreateFromBitmap","(JJIJI)J",                              (void*)nAllocationCubeCreateFromBitmap },
+{"rsnAllocationCreateFromBitmap",    "(JJILandroid/graphics/Bitmap;I)J",      (void*)nAllocationCreateFromBitmap },
+{"rsnAllocationCreateBitmapBackedAllocation",    "(JJILandroid/graphics/Bitmap;I)J", (void*)nAllocationCreateBitmapBackedAllocation },
+{"rsnAllocationCubeCreateFromBitmap","(JJILandroid/graphics/Bitmap;I)J",      (void*)nAllocationCubeCreateFromBitmap },
 
-{"rsnAllocationCopyFromBitmap",      "(JJJ)V",                                (void*)nAllocationCopyFromBitmap },
-{"rsnAllocationCopyToBitmap",        "(JJJ)V",                                (void*)nAllocationCopyToBitmap },
+{"rsnAllocationCopyFromBitmap",      "(JJLandroid/graphics/Bitmap;)V",        (void*)nAllocationCopyFromBitmap },
+{"rsnAllocationCopyToBitmap",        "(JJLandroid/graphics/Bitmap;)V",        (void*)nAllocationCopyToBitmap },
 
 {"rsnAllocationSyncAll",             "(JJI)V",                                (void*)nAllocationSyncAll },
 {"rsnAllocationSetupBufferQueue",    "(JJI)V",                                (void*)nAllocationSetupBufferQueue },
diff --git a/services/Android.bp b/services/Android.bp
index 1e11936..5afed6c 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -113,7 +113,7 @@
     srcs: [":services-sources"],
     installable: false,
     // TODO: remove the --hide options below
-    args: " --show-single-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES,process=android.annotation.SystemApi.Process.SYSTEM_SERVER\\)" +
+    args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES,process=android.annotation.SystemApi.Process.SYSTEM_SERVER\\)" +
         " --hide-annotation android.annotation.Hide" +
         " --hide-package com.google.android.startop.iorap" +
         " --hide ReferencesHidden" +
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 6a6e2b2..58d3489 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -35,6 +35,7 @@
 import android.app.ActivityOptions;
 import android.app.AlertDialog;
 import android.app.PendingIntent;
+import android.app.RemoteAction;
 import android.appwidget.AppWidgetManagerInternal;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
@@ -148,6 +149,8 @@
     //       their capabilities are ready.
     private static final int WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS = 1000;
 
+    static final String FUNCTION_REGISTER_SYSTEM_ACTION = "registerSystemAction";
+    static final String FUNCTION_UNREGISTER_SYSTEM_ACTION = "unregisterSystemAction";
     private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE =
         "registerUiTestAutomationService";
 
@@ -253,6 +256,27 @@
         }
     }
 
+    @VisibleForTesting
+    AccessibilityManagerService(
+            Context context,
+            PackageManager packageManager,
+            AccessibilitySecurityPolicy securityPolicy,
+            SystemActionPerformer systemActionPerformer,
+            AccessibilityWindowManager a11yWindowManager,
+            AccessibilityDisplayListener a11yDisplayListener) {
+        mContext = context;
+        mPowerManager =  (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
+        mMainHandler = new MainHandler(mContext.getMainLooper());
+        mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
+        mPackageManager = packageManager;
+        mSecurityPolicy = securityPolicy;
+        mSystemActionPerformer = systemActionPerformer;
+        mA11yWindowManager = a11yWindowManager;
+        mA11yDisplayListener = a11yDisplayListener;
+        init();
+    }
+
     /**
      * Creates a new instance.
      *
@@ -260,21 +284,24 @@
      */
     public AccessibilityManagerService(Context context) {
         mContext = context;
-        mPackageManager = mContext.getPackageManager();
-        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mPowerManager =  (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
-        mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this);
         mMainHandler = new MainHandler(mContext.getMainLooper());
+        mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
+        mPackageManager = mContext.getPackageManager();
+        mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this);
         mSystemActionPerformer = new SystemActionPerformer(mContext, mWindowManagerService);
         mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler,
                 mWindowManagerService, this, mSecurityPolicy, this);
         mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
-        mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager);
-        mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
+        init();
+    }
 
+    private void init() {
+        mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager);
         registerBroadcastReceivers();
         new AccessibilityContentObserver(mMainHandler).register(
-                context.getContentResolver());
+                mContext.getContentResolver());
     }
 
     @Override
@@ -623,6 +650,30 @@
         event.recycle();
     }
 
+    /**
+     * This is the implementation of AccessibilityManager system API.
+     * System UI calls into this method through AccessibilityManager system API to register a
+     * system action.
+     */
+    @Override
+    public void registerSystemAction(RemoteAction action, int actionId) {
+        mSecurityPolicy.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
+                FUNCTION_REGISTER_SYSTEM_ACTION);
+        mSystemActionPerformer.registerSystemAction(actionId, action);
+    }
+
+    /**
+     * This is the implementation of AccessibilityManager system API.
+     * System UI calls into this method through AccessibilityManager system API to unregister a
+     * system action.
+     */
+    @Override
+    public void unregisterSystemAction(int actionId) {
+        mSecurityPolicy.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
+                FUNCTION_UNREGISTER_SYSTEM_ACTION);
+        mSystemActionPerformer.unregisterSystemAction(actionId);
+    }
+
     @Override
     public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) {
         synchronized (mLock) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index cbff6bd..37ac3ec 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -35,6 +35,7 @@
 import android.util.Slog;
 import android.view.Display;
 
+import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -264,6 +265,24 @@
     }
 
     @Override
+    public boolean switchToInputMethod(String imeId) {
+        synchronized (mLock) {
+            if (!hasRightsToCurrentUserLocked()) {
+                return false;
+            }
+        }
+        final boolean result;
+        final int callingUserId = UserHandle.getCallingUserId();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            result = InputMethodManagerInternal.get().switchToInputMethod(imeId, callingUserId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return result;
+    }
+
+    @Override
     public boolean isAccessibilityButtonAvailable() {
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 1754926..11dcfef 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -157,8 +157,13 @@
     /**
      * This method is called to register a system action. If a system action is already registered
      * with the given id, the existing system action will be overwritten.
+     *
+     * This method is supposed to be package internal since this class is meant to be used by
+     * AccessibilityManagerService only. But Mockito has a bug which requiring this to be public
+     * to be mocked.
      */
-    void registerSystemAction(int id, RemoteAction action) {
+    @VisibleForTesting
+    public void registerSystemAction(int id, RemoteAction action) {
         synchronized (mSystemActionLock) {
             mRegisteredSystemActions.put(id, action);
         }
@@ -170,8 +175,13 @@
     /**
      * This method is called to unregister a system action previously registered through
      * registerSystemAction.
+     *
+     * This method is supposed to be package internal since this class is meant to be used by
+     * AccessibilityManagerService only. But Mockito has a bug which requiring this to be public
+     * to be mocked.
      */
-    void unregisterSystemAction(int id) {
+    @VisibleForTesting
+    public void unregisterSystemAction(int id) {
         synchronized (mSystemActionLock) {
             mRegisteredSystemActions.remove(id);
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 7dd4a70..7a8a112 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -297,6 +297,11 @@
         }
 
         @Override
+        public boolean switchToInputMethod(String imeId) {
+            return false;
+        }
+
+        @Override
         public boolean isAccessibilityButtonAvailable() {
             return false;
         }
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 03d9626..5405fc7 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -331,8 +331,8 @@
     }
 
     @Override // from SystemService
-    public boolean isSupported(UserInfo userInfo) {
-        return userInfo.isFull() || userInfo.isManagedProfile();
+    public boolean isSupportedUser(TargetUser user) {
+        return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile();
     }
 
     @Override // from SystemService
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 95cd8fc..f34b5e7 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -112,7 +112,6 @@
 import com.android.server.inputmethod.InputMethodManagerInternal;
 
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -311,20 +310,9 @@
     @NonNull
     private final InputMethodManagerInternal mInputMethodManagerInternal;
 
-    @GuardedBy("mLock")
-    @Nullable
-    private CompletableFuture<InlineSuggestionsRequest> mSuggestionsRequestFuture;
-
-    @GuardedBy("mLock")
-    @Nullable
-    private CompletableFuture<IInlineSuggestionsResponseCallback>
-            mInlineSuggestionsResponseCallbackFuture;
-
     @Nullable
     private InlineSuggestionsRequestCallbackImpl mInlineSuggestionsRequestCallback;
 
-    private static final int INLINE_REQUEST_TIMEOUT_MS = 1000;
-
     /**
      * Receiver of assist data from the app's {@link Activity}.
      */
@@ -336,7 +324,9 @@
                         + "mForAugmentedAutofillOnly: %s", mForAugmentedAutofillOnly);
                 return;
             }
-            if (mCurrentViewId == null) {
+            // Keeps to prevent it is cleared on multiple threads.
+            final AutofillId currentViewId = mCurrentViewId;
+            if (currentViewId == null) {
                 Slog.w(TAG, "No current view id - session might have finished");
                 return;
             }
@@ -410,7 +400,7 @@
                 if (mContexts == null) {
                     mContexts = new ArrayList<>(1);
                 }
-                mContexts.add(new FillContext(requestId, structure, mCurrentViewId));
+                mContexts.add(new FillContext(requestId, structure, currentViewId));
 
                 cancelCurrentRequestLocked();
 
@@ -422,7 +412,9 @@
                 final ArrayList<FillContext> contexts =
                         mergePreviousSessionLocked(/* forSave= */ false);
 
-                final InlineSuggestionsRequest suggestionsRequest = getInlineSuggestionsRequest();
+                final InlineSuggestionsRequest suggestionsRequest =
+                        mInlineSuggestionsRequestCallback != null
+                                ? mInlineSuggestionsRequestCallback.getRequest() : null;
 
                 request = new FillRequest(requestId, contexts, mClientState, flags,
                         suggestionsRequest);
@@ -620,13 +612,7 @@
     private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState,
             int newState, int flags) {
         if (isInlineSuggestionsEnabled()) {
-            mSuggestionsRequestFuture = new CompletableFuture<>();
-            mInlineSuggestionsResponseCallbackFuture = new CompletableFuture<>();
-
-            if (mInlineSuggestionsRequestCallback == null) {
-                mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl(this);
-            }
-
+            mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl();
             mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
                     mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback);
         }
@@ -636,10 +622,14 @@
 
     private static final class InlineSuggestionsRequestCallbackImpl
             extends IInlineSuggestionsRequestCallback.Stub {
-        private final WeakReference<Session> mSession;
+        private static final int INLINE_REQUEST_TIMEOUT_MS = 1000;
 
-        private InlineSuggestionsRequestCallbackImpl(Session session) {
-            mSession = new WeakReference<>(session);
+        private final CompletableFuture<InlineSuggestionsRequest> mRequest;
+        private final CompletableFuture<IInlineSuggestionsResponseCallback> mResponseCallback;
+
+        private InlineSuggestionsRequestCallbackImpl() {
+            mRequest = new CompletableFuture<>();
+            mResponseCallback = new CompletableFuture<>();
         }
 
         @Override
@@ -648,13 +638,8 @@
                 Log.d(TAG, "inline suggestions request unsupported, "
                         + "falling back to regular autofill");
             }
-            final Session session = mSession.get();
-            if (session != null) {
-                synchronized (session.mLock) {
-                    session.mSuggestionsRequestFuture.cancel(true);
-                    session.mInlineSuggestionsResponseCallbackFuture.cancel(true);
-                }
-            }
+            mRequest.cancel(true);
+            mResponseCallback.cancel(true);
         }
 
         @Override
@@ -663,13 +648,36 @@
             if (sDebug) {
                 Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
             }
-            final Session session = mSession.get();
-            if (session != null) {
-                synchronized (session.mLock) {
-                    session.mSuggestionsRequestFuture.complete(request);
-                    session.mInlineSuggestionsResponseCallbackFuture.complete(callback);
-                }
+            mRequest.complete(request);
+            mResponseCallback.complete(callback);
+        }
+
+        @Nullable
+        private InlineSuggestionsRequest getRequest() {
+            try {
+                return mRequest.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            } catch (TimeoutException e) {
+                Log.w(TAG, "Exception getting inline suggestions request in time: " + e);
+            } catch (CancellationException e) {
+                Log.w(TAG, "Inline suggestions request cancelled");
+            } catch (InterruptedException | ExecutionException e) {
+                throw new RuntimeException(e);
             }
+            return null;
+        }
+
+        @Nullable
+        private IInlineSuggestionsResponseCallback getResponseCallback() {
+            try {
+                return mResponseCallback.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            } catch (TimeoutException e) {
+                Log.w(TAG, "Exception getting inline suggestions callback in time: " + e);
+            } catch (CancellationException e) {
+                Log.w(TAG, "Inline suggestions callback cancelled");
+            } catch (InterruptedException | ExecutionException e) {
+                throw new RuntimeException(e);
+            }
+            return null;
         }
     }
 
@@ -2678,22 +2686,9 @@
      * Returns whether we made a request to show inline suggestions.
      */
     private boolean requestShowInlineSuggestions(FillResponse response) {
-        IInlineSuggestionsResponseCallback inlineContentCallback = null;
-        synchronized (mLock) {
-            if (mInlineSuggestionsResponseCallbackFuture != null) {
-                try {
-                    inlineContentCallback = mInlineSuggestionsResponseCallbackFuture.get(
-                            INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-                } catch (TimeoutException e) {
-                    Log.w(TAG, "Exception getting inline suggestions callback in time: " + e);
-                } catch (CancellationException e) {
-                    Log.w(TAG, "Inline suggestions callback cancelled");
-                } catch (InterruptedException | ExecutionException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        }
-
+        final IInlineSuggestionsResponseCallback inlineContentCallback =
+                mInlineSuggestionsRequestCallback != null
+                        ? mInlineSuggestionsRequestCallback.getResponseCallback() : null;
         if (inlineContentCallback == null) {
             Log.w(TAG, "Session input method callback is not set yet");
             return false;
@@ -3015,10 +3010,12 @@
 
         final AutofillId focusedId = AutofillId.withoutSession(mCurrentViewId);
 
-        final InlineSuggestionsRequest inlineSuggestionsRequest = getInlineSuggestionsRequest();
+        final InlineSuggestionsRequest inlineSuggestionsRequest =
+                mInlineSuggestionsRequestCallback != null
+                        ? mInlineSuggestionsRequestCallback.getRequest() : null;
         final IInlineSuggestionsResponseCallback inlineSuggestionsResponseCallback =
-                getInlineSuggestionsResponseCallback();
-
+                mInlineSuggestionsRequestCallback != null
+                        ? mInlineSuggestionsRequestCallback.getResponseCallback() : null;
         remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId,
                 currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback);
 
@@ -3028,40 +3025,6 @@
         return mAugmentedAutofillDestroyer;
     }
 
-    @Nullable
-    private InlineSuggestionsRequest getInlineSuggestionsRequest() {
-        if (mSuggestionsRequestFuture != null) {
-            try {
-                return mSuggestionsRequestFuture.get(
-                        INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            } catch (TimeoutException e) {
-                Log.w(TAG, "Exception getting inline suggestions request in time: " + e);
-            } catch (CancellationException e) {
-                Log.w(TAG, "Inline suggestions request cancelled");
-            } catch (InterruptedException | ExecutionException e) {
-                throw new RuntimeException(e);
-            }
-        }
-        return null;
-    }
-
-    @Nullable
-    private IInlineSuggestionsResponseCallback getInlineSuggestionsResponseCallback() {
-        if (mInlineSuggestionsResponseCallbackFuture != null) {
-            try {
-                return mInlineSuggestionsResponseCallbackFuture.get(
-                        INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            } catch (TimeoutException e) {
-                Log.w(TAG, "Exception getting inline suggestions callback in time: " + e);
-            } catch (CancellationException e) {
-                Log.w(TAG, "Inline suggestions callback cancelled");
-            } catch (InterruptedException | ExecutionException e) {
-                throw new RuntimeException(e);
-            }
-        }
-        return null;
-    }
-
     @GuardedBy("mLock")
     private void cancelAugmentedAutofillLocked() {
         final RemoteAugmentedAutofillService remoteService = mService
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 3bce322..b13bef2 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1406,10 +1406,7 @@
         long oldId = Binder.clearCallingIdentity();
         final int[] userIds;
         try {
-            userIds =
-                    mContext
-                            .getSystemService(UserManager.class)
-                            .getProfileIds(callingUserId, false);
+            userIds = getUserManager().getProfileIds(callingUserId, false);
         } finally {
             Binder.restoreCallingIdentity(oldId);
         }
@@ -1452,6 +1449,11 @@
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
             return;
         }
+        dumpWithoutCheckingPermission(fd, pw, args);
+    }
+
+    @VisibleForTesting
+    void dumpWithoutCheckingPermission(FileDescriptor fd, PrintWriter pw, String[] args) {
         int userId = binderGetCallingUserId();
         if (!isUserReadyForBackup(userId)) {
             pw.println("Inactive");
@@ -1460,7 +1462,16 @@
 
         if (args != null) {
             for (String arg : args) {
-                if ("users".equals(arg.toLowerCase())) {
+                if ("-h".equals(arg)) {
+                    pw.println("'dumpsys backup' optional arguments:");
+                    pw.println("  -h       : this help text");
+                    pw.println("  a[gents] : dump information about defined backup agents");
+                    pw.println("  transportclients : dump information about transport clients");
+                    pw.println("  transportstats : dump transport statts");
+                    pw.println("  users    : dump the list of users for which backup service "
+                            + "is running");
+                    return;
+                } else if ("users".equals(arg.toLowerCase())) {
                     pw.print(DUMP_RUNNING_USERS_MESSAGE);
                     for (int i = 0; i < mUserServices.size(); i++) {
                         pw.print(" " + mUserServices.keyAt(i));
@@ -1471,11 +1482,12 @@
             }
         }
 
-        UserBackupManagerService userBackupManagerService =
-                getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "dump()");
-
-        if (userBackupManagerService != null) {
-            userBackupManagerService.dump(fd, pw, args);
+        for (int i = 0; i < mUserServices.size(); i++) {
+            UserBackupManagerService userBackupManagerService =
+                    getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), "dump()");
+            if (userBackupManagerService != null) {
+                userBackupManagerService.dump(fd, pw, args);
+            }
         }
     }
 
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 064cd06..7b95ab5 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -3545,14 +3545,7 @@
         try {
             if (args != null) {
                 for (String arg : args) {
-                    if ("-h".equals(arg)) {
-                        pw.println("'dumpsys backup' optional arguments:");
-                        pw.println("  -h       : this help text");
-                        pw.println("  a[gents] : dump information about defined backup agents");
-                        pw.println("  users    : dump the list of users for which backup service "
-                                + "is running");
-                        return;
-                    } else if ("agents".startsWith(arg)) {
+                    if ("agents".startsWith(arg)) {
                         dumpAgents(pw);
                         return;
                     } else if ("transportclients".equals(arg.toLowerCase())) {
@@ -3583,8 +3576,10 @@
     }
 
     private void dumpInternal(PrintWriter pw) {
+        // Add prefix for only non-system users so that system user dumpsys is the same as before
+        String userPrefix = mUserId == UserHandle.USER_SYSTEM ? "" : "User " + mUserId + ":";
         synchronized (mQueueLock) {
-            pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled")
+            pw.println(userPrefix + "Backup Manager is " + (mEnabled ? "enabled" : "disabled")
                     + " / " + (!mSetupComplete ? "not " : "") + "setup complete / "
                     + (this.mPendingInits.size() == 0 ? "not " : "") + "pending init");
             pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled"));
@@ -3594,13 +3589,13 @@
                     + " (now = " + System.currentTimeMillis() + ')');
             pw.println("  next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId));
 
-            pw.println("Transport whitelist:");
+            pw.println(userPrefix + "Transport whitelist:");
             for (ComponentName transport : mTransportManager.getTransportWhitelist()) {
                 pw.print("    ");
                 pw.println(transport.flattenToShortString());
             }
 
-            pw.println("Available transports:");
+            pw.println(userPrefix + "Available transports:");
             final String[] transports = listAllTransports();
             if (transports != null) {
                 for (String t : transports) {
@@ -3626,18 +3621,18 @@
 
             mTransportManager.dumpTransportClients(pw);
 
-            pw.println("Pending init: " + mPendingInits.size());
+            pw.println(userPrefix + "Pending init: " + mPendingInits.size());
             for (String s : mPendingInits) {
                 pw.println("    " + s);
             }
 
-            pw.print("Ancestral: ");
+            pw.print(userPrefix + "Ancestral: ");
             pw.println(Long.toHexString(mAncestralToken));
-            pw.print("Current:   ");
+            pw.print(userPrefix + "Current:   ");
             pw.println(Long.toHexString(mCurrentToken));
 
             int numPackages = mBackupParticipants.size();
-            pw.println("Participants:");
+            pw.println(userPrefix + "Participants:");
             for (int i = 0; i < numPackages; i++) {
                 int uid = mBackupParticipants.keyAt(i);
                 pw.print("  uid: ");
@@ -3648,7 +3643,7 @@
                 }
             }
 
-            pw.println("Ancestral packages: "
+            pw.println(userPrefix + "Ancestral packages: "
                     + (mAncestralPackages == null ? "none" : mAncestralPackages.size()));
             if (mAncestralPackages != null) {
                 for (String pkg : mAncestralPackages) {
@@ -3657,17 +3652,17 @@
             }
 
             Set<String> processedPackages = mProcessedPackagesJournal.getPackagesCopy();
-            pw.println("Ever backed up: " + processedPackages.size());
+            pw.println(userPrefix + "Ever backed up: " + processedPackages.size());
             for (String pkg : processedPackages) {
                 pw.println("    " + pkg);
             }
 
-            pw.println("Pending key/value backup: " + mPendingBackups.size());
+            pw.println(userPrefix + "Pending key/value backup: " + mPendingBackups.size());
             for (BackupRequest req : mPendingBackups.values()) {
                 pw.println("    " + req);
             }
 
-            pw.println("Full backup queue:" + mFullBackupQueue.size());
+            pw.println(userPrefix + "Full backup queue:" + mFullBackupQueue.size());
             for (FullBackupEntry entry : mFullBackupQueue) {
                 pw.print("    ");
                 pw.print(entry.lastBackup);
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 27824af..8eca62a 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -175,8 +175,8 @@
     }
 
     @Override // from SystemService
-    public boolean isSupported(UserInfo userInfo) {
-        return userInfo.isFull() || userInfo.isManagedProfile();
+    public boolean isSupportedUser(TargetUser user) {
+        return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile();
     }
 
     @Override // from SystemService
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a1f57cb..b2fba73 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -165,9 +165,3 @@
     name: "protolog.conf.json.gz",
     src: ":services.core.json.gz",
 }
-
-platform_compat_config {
-    name: "services-core-platform-compat-config",
-    src: ":services.core.unboosted",
-}
-
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 312dd46..368416b 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -41,6 +41,7 @@
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -478,10 +479,12 @@
      *                            will be disabled. Pass in null or an empty list to disable
      *                            all overlays. The order of the items is significant if several
      *                            overlays modify the same resource.
+     * @param outUpdatedPackageNames An output list that contains the package names of packages
+     *                               affected by the update of enabled overlays.
      * @return true if all packages names were known by the package manager, false otherwise
      */
     public abstract boolean setEnabledOverlayPackages(int userId, String targetPackageName,
-            List<String> overlayPackageNames);
+            List<String> overlayPackageNames, Collection<String> outUpdatedPackageNames);
 
     /**
      * Resolves an activity intent, allowing instant apps to be resolved.
@@ -811,6 +814,12 @@
     public abstract boolean isApexPackage(String packageName);
 
     /**
+     * Returns list of {@code packageName} of apks inside the given apex.
+     * @param apexPackageName Package name of the apk container of apex
+     */
+    public abstract List<String> getApksInApex(String apexPackageName);
+
+    /**
      * Uninstalls given {@code packageName}.
      *
      * @param packageName apex package to uninstall.
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index bd8a361..26c12c1 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -84,14 +84,15 @@
 import android.net.NattSocketKeepalive;
 import android.net.Network;
 import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkConfig;
 import android.net.NetworkFactory;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkMisc;
 import android.net.NetworkMonitorManager;
 import android.net.NetworkPolicyManager;
+import android.net.NetworkProvider;
 import android.net.NetworkQuotaInfo;
 import android.net.NetworkRequest;
 import android.net.NetworkScore;
@@ -219,6 +220,7 @@
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * @hide
@@ -362,10 +364,10 @@
     private static final int EVENT_PROXY_HAS_CHANGED = 16;
 
     /**
-     * used internally when registering NetworkFactories
-     * obj = NetworkFactoryInfo
+     * used internally when registering NetworkProviders
+     * obj = NetworkProviderInfo
      */
-    private static final int EVENT_REGISTER_NETWORK_FACTORY = 17;
+    private static final int EVENT_REGISTER_NETWORK_PROVIDER = 17;
 
     /**
      * used internally when registering NetworkAgents
@@ -401,10 +403,10 @@
     private static final int EVENT_RELEASE_NETWORK_REQUEST = 22;
 
     /**
-     * used internally when registering NetworkFactories
+     * used internally when registering NetworkProviders
      * obj = Messenger
      */
-    private static final int EVENT_UNREGISTER_NETWORK_FACTORY = 23;
+    private static final int EVENT_UNREGISTER_NETWORK_PROVIDER = 23;
 
     /**
      * used internally to expire a wakelock when transitioning
@@ -595,6 +597,10 @@
     // sequence number of NetworkRequests
     private int mNextNetworkRequestId = 1;
 
+    // Sequence number for NetworkProvider IDs.
+    private final AtomicInteger mNextNetworkProviderId = new AtomicInteger(
+            NetworkProvider.FIRST_PROVIDER_ID);
+
     // NetworkRequest activity String log entries.
     private static final int MAX_NETWORK_REQUEST_LOGS = 20;
     private final LocalLog mNetworkRequestInfoLogs = new LocalLog(MAX_NETWORK_REQUEST_LOGS);
@@ -2388,9 +2394,9 @@
             return;
         }
 
-        pw.print("NetworkFactories for:");
-        for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
-            pw.print(" " + nfi.name);
+        pw.print("NetworkProviders for:");
+        for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+            pw.print(" " + npi.name);
         }
         pw.println();
         pw.println();
@@ -2625,8 +2631,8 @@
                     if (nai.everConnected) {
                         loge("ERROR: cannot call explicitlySelected on already-connected network");
                     }
-                    nai.networkMisc.explicitlySelected = toBool(msg.arg1);
-                    nai.networkMisc.acceptUnvalidated = toBool(msg.arg1) && toBool(msg.arg2);
+                    nai.networkAgentConfig.explicitlySelected = toBool(msg.arg1);
+                    nai.networkAgentConfig.acceptUnvalidated = toBool(msg.arg1) && toBool(msg.arg2);
                     // Mark the network as temporarily accepting partial connectivity so that it
                     // will be validated (and possibly become default) even if it only provides
                     // partial internet access. Note that if user connects to partial connectivity
@@ -2634,7 +2640,7 @@
                     // out of wifi coverage) and if the same wifi is available again, the device
                     // will auto connect to this wifi even though the wifi has "no internet".
                     // TODO: Evaluate using a separate setting in IpMemoryStore.
-                    nai.networkMisc.acceptPartialConnectivity = toBool(msg.arg2);
+                    nai.networkAgentConfig.acceptPartialConnectivity = toBool(msg.arg2);
                     break;
                 }
                 case NetworkAgent.EVENT_SOCKET_KEEPALIVE: {
@@ -2666,10 +2672,10 @@
                         }
                         // Only show the notification when the private DNS is broken and the
                         // PRIVATE_DNS_BROKEN notification hasn't shown since last valid.
-                        if (privateDnsBroken && !nai.networkMisc.hasShownBroken) {
+                        if (privateDnsBroken && !nai.networkAgentConfig.hasShownBroken) {
                             showNetworkNotification(nai, NotificationType.PRIVATE_DNS_BROKEN);
                         }
-                        nai.networkMisc.hasShownBroken = privateDnsBroken;
+                        nai.networkAgentConfig.hasShownBroken = privateDnsBroken;
                     } else if (nai.networkCapabilities.isPrivateDnsBroken()) {
                         // If probePrivateDnsCompleted is false but nai.networkCapabilities says
                         // private DNS is broken, it means this network is being reevaluated.
@@ -2679,7 +2685,7 @@
                         nai.networkCapabilities.setPrivateDnsBroken(false);
                         final int oldScore = nai.getCurrentScore();
                         updateCapabilities(oldScore, nai, nai.networkCapabilities);
-                        nai.networkMisc.hasShownBroken = false;
+                        nai.networkAgentConfig.hasShownBroken = false;
                     }
                     break;
                 }
@@ -2721,7 +2727,7 @@
                         nai.lastValidated = valid;
                         nai.everValidated |= valid;
                         updateCapabilities(oldScore, nai, nai.networkCapabilities);
-                        // If score has changed, rebroadcast to NetworkFactories. b/17726566
+                        // If score has changed, rebroadcast to NetworkProviders. b/17726566
                         if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
                         if (valid) {
                             handleFreshlyValidatedNetwork(nai);
@@ -2738,7 +2744,7 @@
                             // If network becomes valid, the hasShownBroken should be reset for
                             // that network so that the notification will be fired when the private
                             // DNS is broken again.
-                            nai.networkMisc.hasShownBroken = false;
+                            nai.networkAgentConfig.hasShownBroken = false;
                         }
                     } else if (partialConnectivityChanged) {
                         updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
@@ -2797,9 +2803,10 @@
                             loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
                             break;
                         }
-                        if (!nai.networkMisc.provisioningNotificationDisabled) {
+                        if (!nai.networkAgentConfig.provisioningNotificationDisabled) {
                             mNotifier.showNotification(netId, NotificationType.SIGN_IN, nai, null,
-                                    (PendingIntent) msg.obj, nai.networkMisc.explicitlySelected);
+                                    (PendingIntent) msg.obj,
+                                    nai.networkAgentConfig.explicitlySelected);
                         }
                     }
                     break;
@@ -2836,6 +2843,7 @@
             return true;
         }
 
+        // TODO: delete when direct use of registerNetworkFactory is no longer supported.
         private boolean maybeHandleNetworkFactoryMessage(Message msg) {
             switch (msg.what) {
                 default:
@@ -3025,32 +3033,16 @@
     private void handleAsyncChannelHalfConnect(Message msg) {
         ensureRunningOnConnectivityServiceThread();
         final AsyncChannel ac = (AsyncChannel) msg.obj;
-        if (mNetworkFactoryInfos.containsKey(msg.replyTo)) {
+        if (mNetworkProviderInfos.containsKey(msg.replyTo)) {
             if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
                 if (VDBG) log("NetworkFactory connected");
                 // Finish setting up the full connection
-                mNetworkFactoryInfos.get(msg.replyTo).asyncChannel.sendMessage(
-                        AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
-                // A network factory has connected.  Send it all current NetworkRequests.
-                for (NetworkRequestInfo nri : mNetworkRequests.values()) {
-                    if (nri.request.isListen()) continue;
-                    ensureRunningOnConnectivityServiceThread();
-                    NetworkAgentInfo nai = nri.mSatisfier;
-                    final int score;
-                    final int serial;
-                    if (nai != null) {
-                        score = nai.getCurrentScore();
-                        serial = nai.factorySerialNumber;
-                    } else {
-                        score = 0;
-                        serial = NetworkFactory.SerialNumber.NONE;
-                    }
-                    ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, serial,
-                            nri.request);
-                }
+                NetworkProviderInfo npi = mNetworkProviderInfos.get(msg.replyTo);
+                npi.completeConnection();
+                sendAllRequestsToProvider(npi);
             } else {
                 loge("Error connecting NetworkFactory");
-                mNetworkFactoryInfos.remove(msg.obj);
+                mNetworkProviderInfos.remove(msg.obj);
             }
         } else if (mNetworkAgentInfos.containsKey(msg.replyTo)) {
             if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
@@ -3082,8 +3074,8 @@
         if (nai != null) {
             disconnectAndDestroyNetwork(nai);
         } else {
-            NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo);
-            if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name);
+            NetworkProviderInfo npi = mNetworkProviderInfos.remove(msg.replyTo);
+            if (DBG && npi != null) log("unregisterNetworkFactory for " + npi.name);
         }
     }
 
@@ -3166,7 +3158,7 @@
             // ip[6]tables to flush routes and remove the incoming packet mark rule, so do it
             // after we've rematched networks with requests which should make a potential
             // fallback network the default or requested a new network from the
-            // NetworkFactories, so network traffic isn't interrupted for an unnecessarily
+            // NetworkProviders, so network traffic isn't interrupted for an unnecessarily
             // long time.
             destroyNativeNetwork(nai);
             mDnsManager.removeNetwork(nai.network);
@@ -3179,8 +3171,8 @@
             // This should never fail.  Specifying an already in use NetID will cause failure.
             if (networkAgent.isVPN()) {
                 mNetd.networkCreateVpn(networkAgent.network.netId,
-                        (networkAgent.networkMisc == null
-                                || !networkAgent.networkMisc.allowBypass));
+                        (networkAgent.networkAgentConfig == null
+                                || !networkAgent.networkAgentConfig.allowBypass));
             } else {
                 mNetd.networkCreatePhysical(networkAgent.network.netId,
                         getNetworkPermission(networkAgent.networkCapabilities));
@@ -3429,9 +3421,8 @@
                 }
             }
 
-            for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
-                nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST,
-                        nri.request);
+            for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+                npi.cancelRequest(nri.request);
             }
         } else {
             // listens don't have a singular affectedNetwork.  Check all networks to see
@@ -3481,16 +3472,16 @@
             return;
         }
 
-        if (!nai.networkMisc.explicitlySelected) {
+        if (!nai.networkAgentConfig.explicitlySelected) {
             Slog.wtf(TAG, "BUG: setAcceptUnvalidated non non-explicitly selected network");
         }
 
-        if (accept != nai.networkMisc.acceptUnvalidated) {
-            nai.networkMisc.acceptUnvalidated = accept;
+        if (accept != nai.networkAgentConfig.acceptUnvalidated) {
+            nai.networkAgentConfig.acceptUnvalidated = accept;
             // If network becomes partial connectivity and user already accepted to use this
             // network, we should respect the user's option and don't need to popup the
             // PARTIAL_CONNECTIVITY notification to user again.
-            nai.networkMisc.acceptPartialConnectivity = accept;
+            nai.networkAgentConfig.acceptPartialConnectivity = accept;
             rematchAllNetworksAndRequests();
             sendUpdatedScoreToFactories(nai);
         }
@@ -3527,8 +3518,8 @@
             return;
         }
 
-        if (accept != nai.networkMisc.acceptPartialConnectivity) {
-            nai.networkMisc.acceptPartialConnectivity = accept;
+        if (accept != nai.networkAgentConfig.acceptPartialConnectivity) {
+            nai.networkAgentConfig.acceptPartialConnectivity = accept;
         }
 
         // TODO: Use the current design or save the user choice into IpMemoryStore.
@@ -3738,7 +3729,7 @@
                 action = ConnectivityManager.ACTION_PROMPT_PARTIAL_CONNECTIVITY;
                 // Don't bother the user with a high-priority notification if the network was not
                 // explicitly selected by the user.
-                highPriority = nai.networkMisc.explicitlySelected;
+                highPriority = nai.networkAgentConfig.explicitlySelected;
                 break;
             default:
                 Slog.wtf(TAG, "Unknown notification type " + type);
@@ -3771,14 +3762,15 @@
         // automatically connects to a network that has partial Internet access, the user will
         // always be able to use it, either because they've already chosen "don't ask again" or
         // because we have prompt them.
-        if (nai.partialConnectivity && !nai.networkMisc.acceptPartialConnectivity) {
+        if (nai.partialConnectivity && !nai.networkAgentConfig.acceptPartialConnectivity) {
             return true;
         }
 
         // If a network has no Internet access, only prompt if the network was explicitly selected
         // and if the user has not already told us to use the network regardless of whether it
         // validated or not.
-        if (nai.networkMisc.explicitlySelected && !nai.networkMisc.acceptUnvalidated) {
+        if (nai.networkAgentConfig.explicitlySelected
+                && !nai.networkAgentConfig.acceptUnvalidated) {
             return true;
         }
 
@@ -3866,12 +3858,12 @@
                     handleApplyDefaultProxy((ProxyInfo)msg.obj);
                     break;
                 }
-                case EVENT_REGISTER_NETWORK_FACTORY: {
-                    handleRegisterNetworkFactory((NetworkFactoryInfo)msg.obj);
+                case EVENT_REGISTER_NETWORK_PROVIDER: {
+                    handleRegisterNetworkProvider((NetworkProviderInfo) msg.obj);
                     break;
                 }
-                case EVENT_UNREGISTER_NETWORK_FACTORY: {
-                    handleUnregisterNetworkFactory((Messenger)msg.obj);
+                case EVENT_UNREGISTER_NETWORK_PROVIDER: {
+                    handleUnregisterNetworkProvider((Messenger) msg.obj);
                     break;
                 }
                 case EVENT_REGISTER_NETWORK_AGENT: {
@@ -4916,7 +4908,7 @@
         }
     };
 
-    private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos = new HashMap<>();
+    private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>();
     private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>();
 
     private static final int MAX_NETWORK_REQUESTS_PER_UID = 100;
@@ -4924,18 +4916,73 @@
     @GuardedBy("mUidToNetworkRequestCount")
     private final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray();
 
-    private static class NetworkFactoryInfo {
+    private static class NetworkProviderInfo {
         public final String name;
         public final Messenger messenger;
-        public final AsyncChannel asyncChannel;
-        public final int factorySerialNumber;
+        private final AsyncChannel mAsyncChannel;
+        private final IBinder.DeathRecipient mDeathRecipient;
+        public final int providerId;
 
-        NetworkFactoryInfo(String name, Messenger messenger, AsyncChannel asyncChannel,
-                int factorySerialNumber) {
+        NetworkProviderInfo(String name, Messenger messenger, AsyncChannel asyncChannel,
+                int providerId, IBinder.DeathRecipient deathRecipient) {
             this.name = name;
             this.messenger = messenger;
-            this.asyncChannel = asyncChannel;
-            this.factorySerialNumber = factorySerialNumber;
+            this.providerId = providerId;
+            mAsyncChannel = asyncChannel;
+            mDeathRecipient = deathRecipient;
+
+            if ((mAsyncChannel == null) == (mDeathRecipient == null)) {
+                throw new AssertionError("Must pass exactly one of asyncChannel or deathRecipient");
+            }
+        }
+
+        boolean isLegacyNetworkFactory() {
+            return mAsyncChannel != null;
+        }
+
+        void sendMessageToNetworkProvider(int what, int arg1, int arg2, Object obj) {
+            try {
+                messenger.send(Message.obtain(null /* handler */, what, arg1, arg2, obj));
+            } catch (RemoteException e) {
+                // Remote process died. Ignore; the death recipient will remove this
+                // NetworkProviderInfo from mNetworkProviderInfos.
+            }
+        }
+
+        void requestNetwork(NetworkRequest request, int score, int servingProviderId) {
+            if (isLegacyNetworkFactory()) {
+                mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score,
+                        servingProviderId, request);
+            } else {
+                sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score,
+                            servingProviderId, request);
+            }
+        }
+
+        void cancelRequest(NetworkRequest request) {
+            if (isLegacyNetworkFactory()) {
+                mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST, request);
+            } else {
+                sendMessageToNetworkProvider(NetworkProvider.CMD_CANCEL_REQUEST, 0, 0, request);
+            }
+        }
+
+        void connect(Context context, Handler handler) {
+            if (isLegacyNetworkFactory()) {
+                mAsyncChannel.connect(context, handler, messenger);
+            } else {
+                try {
+                    messenger.getBinder().linkToDeath(mDeathRecipient, 0);
+                } catch (RemoteException e) {
+                    mDeathRecipient.binderDied();
+                }
+            }
+        }
+
+        void completeConnection() {
+            if (isLegacyNetworkFactory()) {
+                mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+            }
         }
     }
 
@@ -5306,6 +5353,11 @@
         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
     }
 
+    /** Returns the next Network provider ID. */
+    public final int nextNetworkProviderId() {
+        return mNextNetworkProviderId.getAndIncrement();
+    }
+
     @Override
     public void releaseNetworkRequest(NetworkRequest networkRequest) {
         ensureNetworkRequestHasType(networkRequest);
@@ -5316,31 +5368,65 @@
     @Override
     public int registerNetworkFactory(Messenger messenger, String name) {
         enforceNetworkFactoryPermission();
-        NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel(),
-                NetworkFactory.SerialNumber.nextSerialNumber());
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi));
-        return nfi.factorySerialNumber;
+        NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, new AsyncChannel(),
+                nextNetworkProviderId(), null /* deathRecipient */);
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi));
+        return npi.providerId;
     }
 
-    private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) {
-        if (DBG) log("Got NetworkFactory Messenger for " + nfi.name);
-        mNetworkFactoryInfos.put(nfi.messenger, nfi);
-        nfi.asyncChannel.connect(mContext, mTrackerHandler, nfi.messenger);
+    private void handleRegisterNetworkProvider(NetworkProviderInfo npi) {
+        if (mNetworkProviderInfos.containsKey(npi.messenger)) {
+            // Avoid creating duplicates. even if an app makes a direct AIDL call.
+            // This will never happen if an app calls ConnectivityManager#registerNetworkProvider,
+            // as that will throw if a duplicate provider is registered.
+            Slog.e(TAG, "Attempt to register existing NetworkProviderInfo "
+                    + mNetworkProviderInfos.get(npi.messenger).name);
+            return;
+        }
+
+        if (DBG) log("Got NetworkProvider Messenger for " + npi.name);
+        mNetworkProviderInfos.put(npi.messenger, npi);
+        npi.connect(mContext, mTrackerHandler);
+        if (!npi.isLegacyNetworkFactory()) {
+            // Legacy NetworkFactories get their requests when their AsyncChannel connects.
+            sendAllRequestsToProvider(npi);
+        }
+    }
+
+    @Override
+    public int registerNetworkProvider(Messenger messenger, String name) {
+        enforceNetworkFactoryPermission();
+        NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger,
+                null /* asyncChannel */, nextNetworkProviderId(),
+                () -> unregisterNetworkProvider(messenger));
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi));
+        return npi.providerId;
+    }
+
+    @Override
+    public void unregisterNetworkProvider(Messenger messenger) {
+        enforceNetworkFactoryPermission();
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger));
     }
 
     @Override
     public void unregisterNetworkFactory(Messenger messenger) {
-        enforceNetworkFactoryPermission();
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_FACTORY, messenger));
+        unregisterNetworkProvider(messenger);
     }
 
-    private void handleUnregisterNetworkFactory(Messenger messenger) {
-        NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(messenger);
-        if (nfi == null) {
-            loge("Failed to find Messenger in unregisterNetworkFactory");
+    private void handleUnregisterNetworkProvider(Messenger messenger) {
+        NetworkProviderInfo npi = mNetworkProviderInfos.remove(messenger);
+        if (npi == null) {
+            loge("Failed to find Messenger in unregisterNetworkProvider");
             return;
         }
-        if (DBG) log("unregisterNetworkFactory for " + nfi.name);
+        if (DBG) log("unregisterNetworkProvider for " + npi.name);
+    }
+
+    @Override
+    public void declareNetworkRequestUnfulfillable(NetworkRequest request) {
+        enforceNetworkFactoryPermission();
+        mHandler.post(() -> handleReleaseNetworkRequest(request, Binder.getCallingUid(), true));
     }
 
     // NOTE: Accessed on multiple threads, must be synchronized on itself.
@@ -5406,9 +5492,9 @@
     // tree.
     public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
             LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
-            int currentScore, NetworkMisc networkMisc) {
+            int currentScore, NetworkAgentConfig networkAgentConfig) {
         return registerNetworkAgent(messenger, networkInfo, linkProperties, networkCapabilities,
-                currentScore, networkMisc, NetworkFactory.SerialNumber.NONE);
+                currentScore, networkAgentConfig, NetworkProvider.ID_NONE);
     }
 
     /**
@@ -5423,12 +5509,12 @@
      *         later : see {@link #updateCapabilities}.
      * @param currentScore the initial score of the network. See
      *         {@link NetworkAgentInfo#getCurrentScore}.
-     * @param networkMisc metadata about the network. This is never updated.
-     * @param factorySerialNumber the serial number of the factory owning this NetworkAgent.
+     * @param networkAgentConfig metadata about the network. This is never updated.
+     * @param providerId the ID of the provider owning this NetworkAgent.
      */
     public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
             LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
-            int currentScore, NetworkMisc networkMisc, int factorySerialNumber) {
+            int currentScore, NetworkAgentConfig networkAgentConfig, int providerId) {
         enforceNetworkFactoryPermission();
 
         LinkProperties lp = new LinkProperties(linkProperties);
@@ -5440,8 +5526,8 @@
         ns.putIntExtension(NetworkScore.LEGACY_SCORE, currentScore);
         final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
                 new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
-                ns, mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd,
-                mDnsResolver, mNMS, factorySerialNumber);
+                ns, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig), this,
+                mNetd, mDnsResolver, mNMS, providerId);
         // Make sure the network capabilities reflect what the agent info says.
         nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
         final String extraInfo = networkInfo.getExtraInfo();
@@ -5720,7 +5806,7 @@
         // Once a NetworkAgent is connected, complain if some immutable capabilities are removed.
          // Don't complain for VPNs since they're not driven by requests and there is no risk of
          // causing a connect/teardown loop.
-         // TODO: remove this altogether and make it the responsibility of the NetworkFactories to
+         // TODO: remove this altogether and make it the responsibility of the NetworkProviders to
          // avoid connect/teardown loops.
         if (nai.everConnected &&
                 !nai.isVPN() &&
@@ -5862,7 +5948,7 @@
             LinkProperties lp) {
         if (nc == null || lp == null) return false;
         return nai.isVPN()
-                && !nai.networkMisc.allowBypass
+                && !nai.networkAgentConfig.allowBypass
                 && nc.getEstablishingVpnAppUid() != Process.SYSTEM_UID
                 && lp.getInterfaceName() != null
                 && (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute());
@@ -5960,9 +6046,27 @@
         if (VDBG || DDBG){
             log("sending new Min Network Score(" + score + "): " + networkRequest.toString());
         }
-        for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
-            nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score,
-                    serial, networkRequest);
+        for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+            npi.requestNetwork(networkRequest, score, serial);
+        }
+    }
+
+    /** Sends all current NetworkRequests to the specified factory. */
+    private void sendAllRequestsToProvider(NetworkProviderInfo npi) {
+        ensureRunningOnConnectivityServiceThread();
+        for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+            if (nri.request.isListen()) continue;
+            NetworkAgentInfo nai = nri.mSatisfier;
+            final int score;
+            final int serial;
+            if (nai != null) {
+                score = nai.getCurrentScore();
+                serial = nai.factorySerialNumber;
+            } else {
+                score = 0;
+                serial = NetworkProvider.ID_NONE;
+            }
+            npi.requestNetwork(nri.request, score, serial);
         }
     }
 
@@ -6243,11 +6347,11 @@
                     Slog.wtf(TAG, "BUG: " + newSatisfier.name() + " already has " + nri.request);
                 }
                 addedRequests.add(nri);
-                // Tell NetworkFactories about the new score, so they can stop
+                // Tell NetworkProviders about the new score, so they can stop
                 // trying to connect if they know they cannot match it.
                 // TODO - this could get expensive if we have a lot of requests for this
                 // network.  Think about if there is a way to reduce this.  Push
-                // netid->request mapping to each factory?
+                // netid->request mapping to each provider?
                 sendUpdatedScoreToFactories(nri.request, newSatisfier);
                 if (isDefaultRequest(nri)) {
                     isNewDefault = true;
@@ -6276,7 +6380,7 @@
                 } else {
                     Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +
                             newNetwork.name() +
-                            " without updating mSatisfier or factories!");
+                            " without updating mSatisfier or providers!");
                 }
                 // TODO: Technically, sending CALLBACK_LOST here is
                 // incorrect if there is a replacement network currently
@@ -6535,7 +6639,7 @@
             // command must be sent after updating LinkProperties to maximize chances of
             // NetworkMonitor seeing the correct LinkProperties when starting.
             // TODO: pass LinkProperties to the NetworkMonitor in the notifyNetworkConnected call.
-            if (networkAgent.networkMisc.acceptPartialConnectivity) {
+            if (networkAgent.networkAgentConfig.acceptPartialConnectivity) {
                 networkAgent.networkMonitor().setAcceptPartialConnectivity();
             }
             networkAgent.networkMonitor().notifyNetworkConnected(
diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java
index bbcfdc6..32cdc41 100644
--- a/services/core/java/com/android/server/GnssManagerService.java
+++ b/services/core/java/com/android/server/GnssManagerService.java
@@ -47,7 +47,6 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocationManagerServiceUtils.LinkedListener;
 import com.android.server.LocationManagerServiceUtils.LinkedListenerBase;
-import com.android.server.location.AbstractLocationProvider;
 import com.android.server.location.CallerIdentity;
 import com.android.server.location.GnssBatchingProvider;
 import com.android.server.location.GnssCapabilitiesProvider;
@@ -116,11 +115,9 @@
     private final Handler mHandler;
 
     public GnssManagerService(LocationManagerService locationManagerService,
-            Context context,
-            AbstractLocationProvider.LocationProviderManager gnssProviderManager,
-            LocationUsageLogger locationUsageLogger) {
-        this(locationManagerService, context, new GnssLocationProvider(context, gnssProviderManager,
-                FgThread.getHandler().getLooper()), locationUsageLogger);
+            Context context, LocationUsageLogger locationUsageLogger) {
+        this(locationManagerService, context,
+                new GnssLocationProvider(context, FgThread.getHandler()), locationUsageLogger);
     }
 
     // Can use this constructor to inject GnssLocationProvider for testing
diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java
index 70569db..5179fa7 100644
--- a/services/core/java/com/android/server/GraphicsStatsService.java
+++ b/services/core/java/com/android/server/GraphicsStatsService.java
@@ -38,11 +38,13 @@
 import android.view.IGraphicsStatsCallback;
 
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FastPrintWriter;
 
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -78,6 +80,8 @@
     private static final int SAVE_BUFFER = 1;
     private static final int DELETE_OLD = 2;
 
+    private static final int AID_STATSD = 1066; // Statsd uid is set to 1066 forever.
+
     // This isn't static because we need this to happen after registerNativeMethods, however
     // the class is loaded (and thus static ctor happens) before that occurs.
     private final int ASHMEM_SIZE = nGetAshmemSize();
@@ -121,6 +125,7 @@
                 return true;
             }
         });
+        nativeInit();
     }
 
     /**
@@ -186,6 +191,86 @@
         return pfd;
     }
 
+    // If lastFullDay is true, pullGraphicsStats returns stats for the last complete day/24h period
+    // that does not include today. If lastFullDay is false, pullGraphicsStats returns stats for the
+    // current day.
+    // This method is invoked from native code only.
+    @SuppressWarnings({"UnusedDeclaration"})
+    private long pullGraphicsStats(boolean lastFullDay) throws RemoteException {
+        int uid = Binder.getCallingUid();
+
+        // DUMP and PACKAGE_USAGE_STATS permissions are required to invoke this method.
+        // TODO: remove exception for statsd daemon after required permissions are granted. statsd
+        // TODO: should have these permissions granted by data/etc/platform.xml, but it does not.
+        if (uid != AID_STATSD) {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new FastPrintWriter(sw);
+            if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
+                pw.flush();
+                throw new RemoteException(sw.toString());
+            }
+        }
+
+        long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            return pullGraphicsStatsImpl(lastFullDay);
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+    }
+
+    private long pullGraphicsStatsImpl(boolean lastFullDay) {
+        long targetDay;
+        if (lastFullDay) {
+            // Get stats from yesterday. Stats stay constant, because the day is over.
+            targetDay = normalizeDate(System.currentTimeMillis() - 86400000).getTimeInMillis();
+        } else {
+            // Get stats from today. Stats may change as more apps are run today.
+            targetDay = normalizeDate(System.currentTimeMillis()).getTimeInMillis();
+        }
+
+        // Find active buffers for targetDay.
+        ArrayList<HistoricalBuffer> buffers;
+        synchronized (mLock) {
+            buffers = new ArrayList<>(mActive.size());
+            for (int i = 0; i < mActive.size(); i++) {
+                ActiveBuffer buffer = mActive.get(i);
+                if (buffer.mInfo.startTime == targetDay) {
+                    try {
+                        buffers.add(new HistoricalBuffer(buffer));
+                    } catch (IOException ex) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+
+        // Dump active and historic buffers for targetDay in a serialized
+        // GraphicsStatsServiceDumpProto proto.
+        long dump = nCreateDump(-1, true);
+        try {
+            synchronized (mFileAccessLock) {
+                HashSet<File> skipList = dumpActiveLocked(dump, buffers);
+                buffers.clear();
+                String subPath = String.format("%d", targetDay);
+                File dateDir = new File(mGraphicsStatsDir, subPath);
+                if (dateDir.exists()) {
+                    for (File pkg : dateDir.listFiles()) {
+                        for (File version : pkg.listFiles()) {
+                            File data = new File(version, "total");
+                            if (skipList.contains(data)) {
+                                continue;
+                            }
+                            nAddToDump(dump, data.getAbsolutePath());
+                        }
+                    }
+                }
+            }
+        } finally {
+            return nFinishDumpInMemory(dump);
+        }
+    }
+
     private ParcelFileDescriptor getPfd(MemoryFile file) {
         try {
             if (!file.getFileDescriptor().valid()) {
@@ -379,12 +464,21 @@
         }
     }
 
+    @Override
+    protected void finalize() throws Throwable {
+        nativeDestructor();
+    }
+
+    private native void nativeInit();
+    private static native void nativeDestructor();
+
     private static native int nGetAshmemSize();
     private static native long nCreateDump(int outFd, boolean isProto);
     private static native void nAddToDump(long dump, String path, String packageName,
             long versionCode, long startTime, long endTime, byte[] data);
     private static native void nAddToDump(long dump, String path);
     private static native void nFinishDump(long dump);
+    private static native long nFinishDumpInMemory(long dump);
     private static native void nSaveBuffer(String path, String packageName, long versionCode,
             long startTime, long endTime, byte[] data);
 
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index c5f1923..dc393d1 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -23,8 +23,6 @@
 import static android.location.LocationManager.PASSIVE_PROVIDER;
 import static android.os.PowerManager.locationPowerSaveModeToString;
 
-import static com.android.internal.util.Preconditions.checkState;
-
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -71,10 +69,8 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
-import android.provider.Settings;
 import android.stats.location.LocationStatsEnums;
 import android.text.TextUtils;
 import android.util.EventLog;
@@ -87,11 +83,11 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.server.location.AbstractLocationProvider;
+import com.android.server.location.AbstractLocationProvider.State;
 import com.android.server.location.ActivityRecognitionProxy;
 import com.android.server.location.CallerIdentity;
 import com.android.server.location.GeocoderProxy;
@@ -105,7 +101,9 @@
 import com.android.server.location.LocationSettingsStore;
 import com.android.server.location.LocationUsageLogger;
 import com.android.server.location.MockProvider;
+import com.android.server.location.MockableLocationProvider;
 import com.android.server.location.PassiveProvider;
+import com.android.server.location.UserInfoStore;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
 import java.io.ByteArrayOutputStream;
@@ -121,6 +119,8 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -193,33 +193,31 @@
     private final Object mLock = new Object();
     private final Context mContext;
     private final Handler mHandler;
+    private final UserInfoStore mUserInfoStore;
     private final LocationSettingsStore mSettingsStore;
     private final LocationUsageLogger mLocationUsageLogger;
 
+    private final PassiveLocationProviderManager mPassiveManager;
+
     private AppOpsManager mAppOps;
     private PackageManager mPackageManager;
     private PowerManager mPowerManager;
     private ActivityManager mActivityManager;
-    private UserManager mUserManager;
 
     private GeofenceManager mGeofenceManager;
     private LocationFudger mLocationFudger;
     private GeocoderProxy mGeocodeProvider;
-    @Nullable
-    private GnssManagerService mGnssManagerService;
-    private PassiveProvider mPassiveProvider; // track passive provider for special cases
+    @Nullable private GnssManagerService mGnssManagerService;
+
     @GuardedBy("mLock")
     private String mExtraLocationControllerPackage;
+    @GuardedBy("mLock")
     private boolean mExtraLocationControllerPackageEnabled;
 
-    // list of currently active providers
-    @GuardedBy("mLock")
-    private final ArrayList<LocationProviderManager> mProviders = new ArrayList<>();
-
-    // list of non-mock providers, so that when mock providers replace real providers, they can be
-    // later re-replaced
-    @GuardedBy("mLock")
-    private final ArrayList<LocationProviderManager> mRealProviders = new ArrayList<>();
+    // @GuardedBy("mLock")
+    // hold lock for write or to prevent write, no lock for read
+    private final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers =
+            new CopyOnWriteArrayList<>();
 
     @GuardedBy("mLock")
     private final HashMap<Object, Receiver> mReceivers = new HashMap<>();
@@ -238,10 +236,6 @@
     private final HashMap<String, Location> mLastLocationCoarseInterval =
             new HashMap<>();
 
-    // current active user on the device - other users are denied location data
-    private int mCurrentUserId = UserHandle.USER_SYSTEM;
-    private int[] mCurrentUserProfiles = new int[]{UserHandle.USER_SYSTEM};
-
     @GuardedBy("mLock")
     @PowerManager.LocationPowerSaveMode
     private int mBatterySaverMode;
@@ -249,9 +243,18 @@
     private LocationManagerService(Context context) {
         mContext = context;
         mHandler = FgThread.getHandler();
+        mUserInfoStore = new UserInfoStore(mContext);
         mSettingsStore = new LocationSettingsStore(mContext, mHandler);
         mLocationUsageLogger = new LocationUsageLogger();
 
+        // set up passive provider -  we do this early because it has no dependencies on system
+        // services or external code that isn't ready yet, and because this allows the variable to
+        // be final. other more complex providers are initialized later, when system services are
+        // ready
+        mPassiveManager = new PassiveLocationProviderManager();
+        mProviderManagers.add(mPassiveManager);
+        mPassiveManager.setRealProvider(new PassiveProvider(mContext));
+
         // Let the package manager query which are the default location
         // providers as they get certain permissions granted by default.
         PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService(
@@ -267,6 +270,7 @@
     }
 
     private void onSystemReady() {
+        mUserInfoStore.onSystemReady();
         mSettingsStore.onSystemReady();
 
         synchronized (mLock) {
@@ -274,7 +278,6 @@
             mAppOps = mContext.getSystemService(AppOpsManager.class);
             mPowerManager = mContext.getSystemService(PowerManager.class);
             mActivityManager = mContext.getSystemService(ActivityManager.class);
-            mUserManager = mContext.getSystemService(UserManager.class);
 
             mLocationFudger = new LocationFudger(mContext, mHandler);
             mGeofenceManager = new GeofenceManager(mContext, mSettingsStore);
@@ -362,10 +365,13 @@
                 }
             }.register(mContext, mHandler.getLooper(), true);
 
+            mUserInfoStore.addListener((oldUserId, newUserId) -> {
+                synchronized (mLock) {
+                    onUserChangedLocked(oldUserId, newUserId);
+                }
+            });
+
             IntentFilter intentFilter = new IntentFilter();
-            intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
-            intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
-            intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
             intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
             intentFilter.addAction(Intent.ACTION_SCREEN_ON);
 
@@ -378,14 +384,6 @@
                     }
                     synchronized (mLock) {
                         switch (action) {
-                            case Intent.ACTION_USER_SWITCHED:
-                                onUserChangedLocked(
-                                        intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
-                                break;
-                            case Intent.ACTION_MANAGED_PROFILE_ADDED:
-                            case Intent.ACTION_MANAGED_PROFILE_REMOVED:
-                                onUserProfilesChangedLocked();
-                                break;
                             case Intent.ACTION_SCREEN_ON:
                             case Intent.ACTION_SCREEN_OFF:
                                 onScreenStateChangedLocked();
@@ -395,11 +393,10 @@
                 }
             }, UserHandle.ALL, intentFilter, null, mHandler);
 
-            // switching the user from null to system here performs the bulk of the initialization
+            // switching the user from null to current here performs the bulk of the initialization
             // work. the user being changed will cause a reload of all user specific settings, which
             // causes initialization, and propagates changes until a steady state is reached
-            mCurrentUserId = UserHandle.USER_NULL;
-            onUserChangedLocked(ActivityManager.getCurrentUser());
+            onUserChangedLocked(UserHandle.USER_NULL, mUserInfoStore.getCurrentUserId());
         }
     }
 
@@ -415,15 +412,15 @@
         for (Receiver receiver : mReceivers.values()) {
             receiver.updateMonitoring(true);
         }
-        for (LocationProviderManager p : mProviders) {
-            applyRequirementsLocked(p);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
     @GuardedBy("mLock")
     private void onPermissionsChangedLocked() {
-        for (LocationProviderManager p : mProviders) {
-            applyRequirementsLocked(p);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
@@ -442,16 +439,16 @@
 
         mBatterySaverMode = newLocationMode;
 
-        for (LocationProviderManager p : mProviders) {
-            applyRequirementsLocked(p);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
     @GuardedBy("mLock")
     private void onScreenStateChangedLocked() {
         if (mBatterySaverMode == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF) {
-            for (LocationProviderManager p : mProviders) {
-                applyRequirementsLocked(p);
+            for (LocationProviderManager manager : mProviderManagers) {
+                applyRequirementsLocked(manager);
             }
         }
     }
@@ -466,8 +463,8 @@
         intent.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, isLocationEnabledForUser(userId));
         mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
 
-        for (LocationProviderManager p : mProviders) {
-            p.onUseableChangedLocked(userId);
+        for (LocationProviderManager manager : mProviderManagers) {
+            manager.onUseableChangedLocked(userId);
         }
     }
 
@@ -521,36 +518,26 @@
 
     @GuardedBy("mLock")
     private void onBackgroundThrottleIntervalChangedLocked() {
-        for (LocationProviderManager provider : mProviders) {
-            applyRequirementsLocked(provider);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
     @GuardedBy("mLock")
     private void onBackgroundThrottleWhitelistChangedLocked() {
-        for (LocationProviderManager p : mProviders) {
-            applyRequirementsLocked(p);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
     @GuardedBy("lock")
     private void onIgnoreSettingsWhitelistChangedLocked() {
-        for (LocationProviderManager p : mProviders) {
-            applyRequirementsLocked(p);
+        for (LocationProviderManager manager : mProviderManagers) {
+            applyRequirementsLocked(manager);
         }
     }
 
     @GuardedBy("mLock")
-    private void onUserProfilesChangedLocked() {
-        mCurrentUserProfiles = mUserManager.getProfileIdsWithDisabled(mCurrentUserId);
-    }
-
-    @GuardedBy("mLock")
-    private boolean isCurrentProfileLocked(int userId) {
-        return ArrayUtils.contains(mCurrentUserProfiles, userId);
-    }
-
-    @GuardedBy("mLock")
     private void ensureFallbackFusedProviderPresentLocked(String[] pkgs) {
         PackageManager pm = mContext.getPackageManager();
         String systemPackageName = mContext.getPackageName();
@@ -558,7 +545,7 @@
 
         List<ResolveInfo> rInfos = pm.queryIntentServicesAsUser(
                 new Intent(FUSED_LOCATION_SERVICE_ACTION),
-                PackageManager.GET_META_DATA, mCurrentUserId);
+                PackageManager.GET_META_DATA, mUserInfoStore.getCurrentUserId());
         for (ResolveInfo rInfo : rInfos) {
             String packageName = rInfo.serviceInfo.packageName;
 
@@ -623,22 +610,11 @@
 
     @GuardedBy("mLock")
     private void initializeProvidersLocked() {
-        // create a passive location provider, which is always enabled
-        LocationProviderManager passiveProviderManager = new LocationProviderManager(
-                PASSIVE_PROVIDER);
-        addProviderLocked(passiveProviderManager);
-        mPassiveProvider = new PassiveProvider(mContext, passiveProviderManager);
-        passiveProviderManager.attachLocked(mPassiveProvider);
-
         if (GnssManagerService.isGnssSupported()) {
-            // Create a gps location provider manager
-            LocationProviderManager gnssProviderManager = new LocationProviderManager(GPS_PROVIDER);
-            mRealProviders.add(gnssProviderManager);
-            addProviderLocked(gnssProviderManager);
-
-            mGnssManagerService = new GnssManagerService(this, mContext, gnssProviderManager,
-                    mLocationUsageLogger);
-            gnssProviderManager.attachLocked(mGnssManagerService.getGnssLocationProvider());
+            mGnssManagerService = new GnssManagerService(this, mContext, mLocationUsageLogger);
+            LocationProviderManager gnssManager = new LocationProviderManager(GPS_PROVIDER);
+            mProviderManagers.add(gnssManager);
+            gnssManager.setRealProvider(mGnssManagerService.getGnssLocationProvider());
         }
 
         /*
@@ -662,37 +638,31 @@
 
         ensureFallbackFusedProviderPresentLocked(pkgs);
 
-        // bind to network provider
-        LocationProviderManager networkProviderManager = new LocationProviderManager(
-                NETWORK_PROVIDER);
         LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind(
                 mContext,
-                networkProviderManager,
                 NETWORK_LOCATION_SERVICE_ACTION,
                 com.android.internal.R.bool.config_enableNetworkLocationOverlay,
                 com.android.internal.R.string.config_networkLocationProviderPackageName,
                 com.android.internal.R.array.config_locationProviderPackageNames);
         if (networkProvider != null) {
-            mRealProviders.add(networkProviderManager);
-            addProviderLocked(networkProviderManager);
-            networkProviderManager.attachLocked(networkProvider);
+            LocationProviderManager networkManager = new LocationProviderManager(NETWORK_PROVIDER);
+            mProviderManagers.add(networkManager);
+            networkManager.setRealProvider(networkProvider);
         } else {
             Slog.w(TAG, "no network location provider found");
         }
 
         // bind to fused provider
-        LocationProviderManager fusedProviderManager = new LocationProviderManager(FUSED_PROVIDER);
         LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind(
                 mContext,
-                fusedProviderManager,
                 FUSED_LOCATION_SERVICE_ACTION,
                 com.android.internal.R.bool.config_enableFusedLocationOverlay,
                 com.android.internal.R.string.config_fusedLocationProviderPackageName,
                 com.android.internal.R.array.config_locationProviderPackageNames);
         if (fusedProvider != null) {
-            mRealProviders.add(fusedProviderManager);
-            addProviderLocked(fusedProviderManager);
-            fusedProviderManager.attachLocked(fusedProvider);
+            LocationProviderManager fusedManager = new LocationProviderManager(FUSED_PROVIDER);
+            mProviderManagers.add(fusedManager);
+            fusedManager.setRealProvider(fusedProvider);
         } else {
             Slog.e(TAG, "no fused location provider found",
                     new IllegalStateException("Location service needs a fused location provider"));
@@ -754,250 +724,200 @@
                     Boolean.parseBoolean(fragments[7]) /* supportsBearing */,
                     Integer.parseInt(fragments[8]) /* powerRequirement */,
                     Integer.parseInt(fragments[9]) /* accuracy */);
-            LocationProviderManager testProviderManager = new LocationProviderManager(name);
-            addProviderLocked(testProviderManager);
-            testProviderManager.attachLocked(
-                    new MockProvider(mContext, testProviderManager, properties));
+            addTestProvider(name, properties, mContext.getOpPackageName());
         }
     }
 
     @GuardedBy("mLock")
-    private void onUserChangedLocked(int userId) {
-        if (mCurrentUserId == userId) {
-            return;
-        }
-
+    private void onUserChangedLocked(int oldUserId, int newUserId) {
         if (D) {
-            Log.d(TAG, "foreground user is changing to " + userId);
+            Log.d(TAG, "foreground user is changing to " + newUserId);
         }
 
-        int oldUserId = userId;
-        mCurrentUserId = userId;
-        onUserProfilesChangedLocked();
+        for (LocationProviderManager manager : mProviderManagers) {
+            // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
+            mSettingsStore.setLocationProviderAllowed(manager.getName(),
+                    manager.isUseable(newUserId), newUserId);
 
-        // let providers know the current user has changed
-        for (LocationProviderManager p : mProviders) {
-            p.onUseableChangedLocked(oldUserId);
-            p.onUseableChangedLocked(mCurrentUserId);
+            manager.onUseableChangedLocked(oldUserId);
+            manager.onUseableChangedLocked(newUserId);
         }
     }
 
     /**
      * Location provider manager, manages a LocationProvider.
      */
-    class LocationProviderManager implements AbstractLocationProvider.LocationProviderManager {
+    class LocationProviderManager implements MockableLocationProvider.Listener {
 
         private final String mName;
 
-        // remember to clear binder identity before invoking any provider operation
-        @GuardedBy("mLock")
-        @Nullable
-        protected AbstractLocationProvider mProvider;
+        // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
+        protected final MockableLocationProvider mProvider;
 
+        // useable state for parent user ids, no entry implies false. location state is only kept
+        // for parent user ids, the location state for a profile user id is assumed to be the same
+        // as for the parent. if querying this structure, ensure that the user id being used is a
+        // parent id or the results may be incorrect.
         @GuardedBy("mLock")
-        private SparseArray<Boolean> mUseable;  // combined state for each user id
-        @GuardedBy("mLock")
-        private boolean mEnabled;  // state of provider
-
-        @GuardedBy("mLock")
-        @Nullable
-        private ProviderProperties mProperties;
+        private final SparseArray<Boolean> mUseable;
 
         private LocationProviderManager(String name) {
             mName = name;
-
-            mProvider = null;
             mUseable = new SparseArray<>(1);
-            mEnabled = false;
-            mProperties = null;
 
-            // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
-            Settings.Secure.putStringForUser(
-                    mContext.getContentResolver(),
-                    Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                    "-" + mName,
-                    mCurrentUserId);
-        }
+            // initialize last since this lets our reference escape
+            mProvider = new MockableLocationProvider(mContext, mLock, this);
 
-        @GuardedBy("mLock")
-        public void attachLocked(AbstractLocationProvider provider) {
-            Objects.requireNonNull(provider);
-            checkState(mProvider == null);
-
-            if (D) {
-                Log.d(TAG, mName + " provider attached");
-            }
-
-            mProvider = provider;
-
-            // it would be more correct to call this for all users, but we know this can only
-            // affect the current user since providers are disabled for non-current users
-            onUseableChangedLocked(mCurrentUserId);
+            // we can assume all users start with unuseable location state since the initial state
+            // of all providers is disabled. no need to initialize mUseable further.
         }
 
         public String getName() {
             return mName;
         }
 
-        @GuardedBy("mLock")
-        public List<String> getPackagesLocked() {
-            if (mProvider == null) {
-                return Collections.emptyList();
-            } else {
-                // safe to not clear binder context since this doesn't call into the real provider
-                return mProvider.getProviderPackages();
-            }
+        public boolean hasProvider() {
+            return mProvider.getProvider() != null;
         }
 
-        public boolean isMock() {
-            return false;
+        public void setRealProvider(AbstractLocationProvider provider) {
+            mProvider.setRealProvider(provider);
         }
 
-        @GuardedBy("mLock")
-        public boolean isPassiveLocked() {
-            return mProvider == mPassiveProvider;
+        public void setMockProvider(@Nullable MockProvider provider) {
+            mProvider.setMockProvider(provider);
         }
 
-        @GuardedBy("mLock")
+        public Set<String> getPackages() {
+            return mProvider.getState().providerPackageNames;
+        }
+
         @Nullable
-        public ProviderProperties getPropertiesLocked() {
-            return mProperties;
+        public ProviderProperties getProperties() {
+            return mProvider.getState().properties;
         }
 
-        public void setRequest(ProviderRequest request, WorkSource workSource) {
-            // move calls going to providers onto a different thread to avoid deadlock
-            mHandler.post(() -> {
-                synchronized (mLock) {
-                    if (mProvider != null) {
-                        mProvider.onSetRequest(request, workSource);
-                    }
+        public void setMockProviderEnabled(boolean enabled) {
+            synchronized (mLock) {
+                if (!mProvider.isMock()) {
+                    throw new IllegalArgumentException(mName + " provider is not a test provider");
                 }
-            });
+
+                mProvider.setMockProviderEnabled(enabled);
+            }
         }
 
-        public void sendExtraCommand(String command, Bundle extras) {
-            int uid = Binder.getCallingUid();
-            int pid = Binder.getCallingPid();
-
-            // move calls going to providers onto a different thread to avoid deadlock
-            mHandler.post(() -> {
-                synchronized (mLock) {
-                    if (mProvider != null) {
-                        mProvider.onSendExtraCommand(uid, pid, command, extras);
-                    }
+        public void setMockProviderLocation(Location location) {
+            synchronized (mLock) {
+                if (!mProvider.isMock()) {
+                    throw new IllegalArgumentException(mName + " provider is not a test provider");
                 }
-            });
+
+                String locationProvider = location.getProvider();
+                if (!TextUtils.isEmpty(locationProvider) && !mName.equals(locationProvider)) {
+                    // The location has an explicit provider that is different from the mock
+                    // provider name. The caller may be trying to fool us via b/33091107.
+                    EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
+                            mName + "!=" + locationProvider);
+                }
+
+                mProvider.setMockProviderLocation(location);
+            }
+        }
+
+        public List<LocationRequest> getMockProviderRequests() {
+            synchronized (mLock) {
+                if (!mProvider.isMock()) {
+                    throw new IllegalArgumentException(mName + " provider is not a test provider");
+                }
+
+                return mProvider.getCurrentRequest().locationRequests;
+            }
+        }
+
+        public void setRequest(ProviderRequest request) {
+            mProvider.setRequest(request);
+        }
+
+        public void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
+            mProvider.sendExtraCommand(uid, pid, command, extras);
         }
 
         @GuardedBy("mLock")
-        public void dumpLocked(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
-            pw.print(mName + " provider");
-            if (isMock()) {
-                pw.print(" [mock]");
-            }
-            pw.println(":");
-
-            pw.increaseIndent();
-
-            pw.println("useable=" + isUseableLocked(mCurrentUserId));
-            if (!isUseableLocked(mCurrentUserId)) {
-                pw.println("attached=" + (mProvider != null));
-                pw.println("enabled=" + mEnabled);
-            }
-
-            pw.println("properties=" + mProperties);
-
-            if (mProvider != null) {
-                // in order to be consistent with other provider APIs, this should be run on the
-                // location thread... but this likely isn't worth it just for dumping info.
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    mProvider.dump(fd, pw, args);
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            }
-
-            pw.decreaseIndent();
-        }
-
         @Override
         public void onReportLocation(Location location) {
-            // likelihood of a 0,0 bug is far greater than this being a valid location
-            if (!isMock() && location.getLatitude() == 0 && location.getLongitude() == 0) {
-                Slog.w(TAG, "blocking 0,0 location from " + mName + " provider");
-                return;
+            // don't validate mock locations
+            if (!location.isFromMockProvider()) {
+                if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+                    Slog.w(TAG, "blocking 0,0 location from " + mName + " provider");
+                    return;
+                }
             }
 
-            synchronized (mLock) {
-                handleLocationChangedLocked(location, this);
-            }
+            handleLocationChangedLocked(location, this);
         }
 
+        @GuardedBy("mLock")
         @Override
         public void onReportLocation(List<Location> locations) {
             if (mGnssManagerService == null) {
                 return;
             }
-            synchronized (mLock) {
-                LocationProviderManager gpsProvider = getLocationProviderLocked(GPS_PROVIDER);
-                if (gpsProvider == null || !gpsProvider.isUseableLocked()) {
-                    Slog.w(TAG, "reportLocationBatch() called without user permission");
-                    return;
-                }
 
-                mGnssManagerService.onReportLocation(locations);
+            if (!GPS_PROVIDER.equals(mName) || !isUseable()) {
+                Slog.w(TAG, "reportLocationBatch() called without user permission");
+                return;
             }
-        }
 
-        @Override
-        public void onSetEnabled(boolean enabled) {
-            synchronized (mLock) {
-                if (enabled == mEnabled) {
-                    return;
-                }
-
-                if (D) {
-                    Log.d(TAG, mName + " provider enabled is now " + mEnabled);
-                }
-
-                mEnabled = enabled;
-
-                // it would be more correct to call this for all users, but we know this can only
-                // affect the current user since providers are disabled for non-current users
-                onUseableChangedLocked(mCurrentUserId);
-            }
-        }
-
-        @Override
-        public void onSetProperties(ProviderProperties properties) {
-            synchronized (mLock) {
-                mProperties = properties;
-            }
+            mGnssManagerService.onReportLocation(locations);
         }
 
         @GuardedBy("mLock")
-        public boolean isUseableLocked() {
-            return isUseableLocked(mCurrentUserId);
+        @Override
+        public void onStateChanged(State oldState, State newState) {
+            if (oldState.enabled != newState.enabled) {
+                // it would be more correct to call this for all users, but we know this can
+                // only affect the current user since providers are disabled for non-current
+                // users
+                onUseableChangedLocked(mUserInfoStore.getCurrentUserId());
+            }
         }
 
-        @GuardedBy("mLock")
-        public boolean isUseableLocked(int userId) {
-            return mUseable.get(userId, Boolean.FALSE);
+        public boolean isUseable() {
+            return isUseable(mUserInfoStore.getCurrentUserId());
+        }
+
+        public boolean isUseable(int userId) {
+            synchronized (mLock) {
+                // normalize user id to always refer to parent since profile state is always the
+                // same as parent state
+                userId = mUserInfoStore.getParentUserId(userId);
+
+                return mUseable.get(userId, Boolean.FALSE);
+            }
         }
 
         @GuardedBy("mLock")
         public void onUseableChangedLocked(int userId) {
+            if (userId == UserHandle.USER_NULL) {
+                // only used during initialization - we don't care about the null user
+                return;
+            }
+
+            // normalize user id to always refer to parent since profile state is always the same
+            // as parent state
+            userId = mUserInfoStore.getParentUserId(userId);
+
             // if any property that contributes to "useability" here changes state, it MUST result
             // in a direct or indrect call to onUseableChangedLocked. this allows the provider to
             // guarantee that it will always eventually reach the correct state.
-            boolean useable = mProvider != null && mProviders.contains(this)
-                    && isCurrentProfileLocked(userId) && isLocationEnabledForUser(userId)
-                    && mEnabled;
+            boolean useable = (userId == mUserInfoStore.getCurrentUserId())
+                    && mSettingsStore.isLocationEnabled(userId) && mProvider.getState().enabled;
 
-            if (useable == isUseableLocked(userId)) {
+            if (useable == isUseable(userId)) {
                 return;
             }
+
             mUseable.put(userId, useable);
 
             if (D) {
@@ -1007,11 +927,7 @@
             // fused and passive provider never get public updates for legacy reasons
             if (!FUSED_PROVIDER.equals(mName) && !PASSIVE_PROVIDER.equals(mName)) {
                 // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
-                Settings.Secure.putStringForUser(
-                        mContext.getContentResolver(),
-                        Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                        (useable ? "+" : "-") + mName,
-                        userId);
+                mSettingsStore.setLocationProviderAllowed(mName, useable, userId);
 
                 Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION);
                 intent.putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName);
@@ -1029,55 +945,64 @@
 
             updateProviderUseableLocked(this);
         }
+
+        public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
+            synchronized (mLock) {
+                pw.print(mName + " provider");
+                if (mProvider.isMock()) {
+                    pw.print(" [mock]");
+                }
+                pw.println(":");
+
+                pw.increaseIndent();
+
+                boolean useable = isUseable();
+                pw.println("useable=" + useable);
+                if (!useable) {
+                    pw.println("enabled=" + mProvider.getState().enabled);
+                }
+
+                pw.println("properties=" + mProvider.getState().properties);
+            }
+
+            mProvider.dump(fd, pw, args);
+
+            pw.decreaseIndent();
+        }
     }
 
-    private class MockLocationProvider extends LocationProviderManager {
+    class PassiveLocationProviderManager extends LocationProviderManager {
 
-        private ProviderRequest mCurrentRequest;
-
-        private MockLocationProvider(String name) {
-            super(name);
+        private PassiveLocationProviderManager() {
+            super(PASSIVE_PROVIDER);
         }
 
         @Override
-        public void attachLocked(AbstractLocationProvider provider) {
-            checkState(provider instanceof MockProvider);
-            super.attachLocked(provider);
+        public void setRealProvider(AbstractLocationProvider provider) {
+            Preconditions.checkArgument(provider instanceof PassiveProvider);
+            super.setRealProvider(provider);
         }
 
-        public boolean isMock() {
-            return true;
+        @Override
+        public void setMockProvider(@Nullable MockProvider provider) {
+            if (provider != null) {
+                throw new IllegalArgumentException("Cannot mock the passive provider");
+            }
         }
 
-        @GuardedBy("mLock")
-        public void setEnabledLocked(boolean enabled) {
-            if (mProvider != null) {
+        public void updateLocation(Location location) {
+            synchronized (mLock) {
+                PassiveProvider passiveProvider = (PassiveProvider) mProvider.getProvider();
+                Preconditions.checkState(passiveProvider != null);
+
                 long identity = Binder.clearCallingIdentity();
                 try {
-                    ((MockProvider) mProvider).setEnabled(enabled);
+                    passiveProvider.updateLocation(location);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
         }
-
-        @GuardedBy("mLock")
-        public void setLocationLocked(Location location) {
-            if (mProvider != null) {
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    ((MockProvider) mProvider).setLocation(location);
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-            }
-        }
-
-        @Override
-        public void setRequest(ProviderRequest request, WorkSource workSource) {
-            super.setRequest(request, workSource);
-            mCurrentRequest = request;
-        }
     }
 
     /**
@@ -1181,17 +1106,17 @@
                 // See if receiver has any enabled update records.  Also note if any update records
                 // are high power (has a high power provider with an interval under a threshold).
                 for (UpdateRecord updateRecord : mUpdateRecords.values()) {
-                    LocationProviderManager provider = getLocationProviderLocked(
+                    LocationProviderManager manager = getLocationProviderManager(
                             updateRecord.mProvider);
-                    if (provider == null) {
+                    if (manager == null) {
                         continue;
                     }
-                    if (!provider.isUseableLocked() && !isSettingsExemptLocked(updateRecord)) {
+                    if (!manager.isUseable() && !isSettingsExemptLocked(updateRecord)) {
                         continue;
                     }
 
                     requestingLocation = true;
-                    ProviderProperties properties = provider.getPropertiesLocked();
+                    ProviderProperties properties = manager.getProperties();
                     if (properties != null
                             && properties.mPowerRequirement == Criteria.POWER_HIGH
                             && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) {
@@ -1432,7 +1357,7 @@
             String featureId, String listenerIdentifier) {
         Objects.requireNonNull(listenerIdentifier);
 
-        return mGnssManagerService == null ? false : mGnssManagerService.addGnssBatchingCallback(
+        return mGnssManagerService != null && mGnssManagerService.addGnssBatchingCallback(
                 callback, packageName, featureId, listenerIdentifier);
     }
 
@@ -1443,7 +1368,7 @@
 
     @Override
     public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName) {
-        return mGnssManagerService == null ? false : mGnssManagerService.startGnssBatch(periodNanos,
+        return mGnssManagerService != null && mGnssManagerService.startGnssBatch(periodNanos,
                 wakeOnFifoFull, packageName);
     }
 
@@ -1454,35 +1379,14 @@
 
     @Override
     public boolean stopGnssBatch() {
-        return mGnssManagerService == null ? false : mGnssManagerService.stopGnssBatch();
+        return mGnssManagerService != null && mGnssManagerService.stopGnssBatch();
     }
 
-    @GuardedBy("mLock")
-    private void addProviderLocked(LocationProviderManager provider) {
-        Preconditions.checkState(getLocationProviderLocked(provider.getName()) == null);
-
-        mProviders.add(provider);
-
-        // it would be more correct to call this for all users, but we know this can only
-        // affect the current user since providers are disabled for non-current users
-        provider.onUseableChangedLocked(mCurrentUserId);
-    }
-
-    @GuardedBy("mLock")
-    private void removeProviderLocked(LocationProviderManager provider) {
-        if (mProviders.remove(provider)) {
-            // it would be more correct to call this for all users, but we know this can only
-            // affect the current user since providers are disabled for non-current users
-            provider.onUseableChangedLocked(mCurrentUserId);
-        }
-    }
-
-    @GuardedBy("mLock")
     @Nullable
-    private LocationProviderManager getLocationProviderLocked(String providerName) {
-        for (LocationProviderManager provider : mProviders) {
-            if (providerName.equals(provider.getName())) {
-                return provider;
+    private LocationProviderManager getLocationProviderManager(String providerName) {
+        for (LocationProviderManager manager : mProviderManagers) {
+            if (providerName.equals(manager.getName())) {
+                return manager;
             }
         }
 
@@ -1531,12 +1435,12 @@
             // network and fused providers are ok with COARSE or FINE
             return RESOLUTION_LEVEL_COARSE;
         } else {
-            for (LocationProviderManager lp : mProviders) {
+            for (LocationProviderManager lp : mProviderManagers) {
                 if (!lp.getName().equals(provider)) {
                     continue;
                 }
 
-                ProviderProperties properties = lp.getPropertiesLocked();
+                ProviderProperties properties = lp.getProperties();
                 if (properties != null) {
                     if (properties.mRequiresSatellite) {
                         // provider requiring satellites require FINE permission
@@ -1587,11 +1491,9 @@
             case RESOLUTION_LEVEL_COARSE:
                 return AppOpsManager.OPSTR_COARSE_LOCATION;
             case RESOLUTION_LEVEL_FINE:
-                return AppOpsManager.OPSTR_FINE_LOCATION;
+                // fall through
             case RESOLUTION_LEVEL_NONE:
-                // The client is not allowed to get any location, so both FINE and COARSE ops will
-                // be denied. Pick the most restrictive one to be safe.
-                return AppOpsManager.OPSTR_FINE_LOCATION;
+                // fall through
             default:
                 // Use the most restrictive ops if not sure.
                 return AppOpsManager.OPSTR_FINE_LOCATION;
@@ -1629,17 +1531,14 @@
      */
     @Override
     public List<String> getAllProviders() {
-        synchronized (mLock) {
-            ArrayList<String> providers = new ArrayList<>(mProviders.size());
-            for (LocationProviderManager provider : mProviders) {
-                String name = provider.getName();
-                if (FUSED_PROVIDER.equals(name)) {
-                    continue;
-                }
-                providers.add(name);
+        ArrayList<String> providers = new ArrayList<>(mProviderManagers.size());
+        for (LocationProviderManager manager : mProviderManagers) {
+            if (FUSED_PROVIDER.equals(manager.getName())) {
+                continue;
             }
-            return providers;
+            providers.add(manager.getName());
         }
+        return providers;
     }
 
     /**
@@ -1651,21 +1550,21 @@
     public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
         int allowedResolutionLevel = getCallerAllowedResolutionLevel();
         synchronized (mLock) {
-            ArrayList<String> providers = new ArrayList<>(mProviders.size());
-            for (LocationProviderManager provider : mProviders) {
-                String name = provider.getName();
+            ArrayList<String> providers = new ArrayList<>(mProviderManagers.size());
+            for (LocationProviderManager manager : mProviderManagers) {
+                String name = manager.getName();
                 if (FUSED_PROVIDER.equals(name)) {
                     continue;
                 }
                 if (allowedResolutionLevel < getMinimumResolutionLevelForProviderUseLocked(name)) {
                     continue;
                 }
-                if (enabledOnly && !provider.isUseableLocked()) {
+                if (enabledOnly && !manager.isUseable()) {
                     continue;
                 }
                 if (criteria != null
                         && !android.location.LocationProvider.propertiesMeetCriteria(
-                        name, provider.getPropertiesLocked(), criteria)) {
+                        name, manager.getProperties(), criteria)) {
                     continue;
                 }
                 providers.add(name);
@@ -1702,15 +1601,15 @@
     }
 
     @GuardedBy("mLock")
-    private void updateProviderUseableLocked(LocationProviderManager provider) {
-        boolean useable = provider.isUseableLocked();
+    private void updateProviderUseableLocked(LocationProviderManager manager) {
+        boolean useable = manager.isUseable();
 
         ArrayList<Receiver> deadReceivers = null;
 
-        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
+        ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
         if (records != null) {
             for (UpdateRecord record : records) {
-                if (!isCurrentProfileLocked(
+                if (!mUserInfoStore.isCurrentUserOrProfile(
                         UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
                     continue;
                 }
@@ -1721,7 +1620,7 @@
                 }
 
                 // Sends a notification message to the receiver
-                if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) {
+                if (!record.mReceiver.callProviderEnabledLocked(manager.getName(), useable)) {
                     if (deadReceivers == null) {
                         deadReceivers = new ArrayList<>();
                     }
@@ -1736,26 +1635,25 @@
             }
         }
 
-        applyRequirementsLocked(provider);
+        applyRequirementsLocked(manager);
     }
 
     @GuardedBy("mLock")
     private void applyRequirementsLocked(String providerName) {
-        LocationProviderManager provider = getLocationProviderLocked(providerName);
-        if (provider != null) {
-            applyRequirementsLocked(provider);
+        LocationProviderManager manager = getLocationProviderManager(providerName);
+        if (manager != null) {
+            applyRequirementsLocked(manager);
         }
     }
 
     @GuardedBy("mLock")
-    private void applyRequirementsLocked(LocationProviderManager provider) {
-        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
-        WorkSource worksource = new WorkSource();
-        ProviderRequest providerRequest = new ProviderRequest();
+    private void applyRequirementsLocked(LocationProviderManager manager) {
+        ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
+        ProviderRequest.Builder providerRequest = new ProviderRequest.Builder();
 
         // if provider is not active, it should not respond to requests
 
-        if (mProviders.contains(provider) && records != null && !records.isEmpty()) {
+        if (mProviderManagers.contains(manager) && records != null && !records.isEmpty()) {
             long backgroundThrottleInterval;
 
             long identity = Binder.clearCallingIdentity();
@@ -1765,6 +1663,8 @@
                 Binder.restoreCallingIdentity(identity);
             }
 
+            ArrayList<LocationRequest> requests = new ArrayList<>(records.size());
+
             final boolean isForegroundOnlyMode =
                     mBatterySaverMode == PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
             final boolean shouldThrottleRequests =
@@ -1772,9 +1672,9 @@
                             == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF
                             && !mPowerManager.isInteractive();
             // initialize the low power mode to true and set to false if any of the records requires
-            providerRequest.lowPowerMode = true;
+            providerRequest.setLowPowerMode(true);
             for (UpdateRecord record : records) {
-                if (!isCurrentProfileLocked(
+                if (!mUserInfoStore.isCurrentUserOrProfile(
                         UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
                     continue;
                 }
@@ -1787,10 +1687,10 @@
                 }
                 final boolean isBatterySaverDisablingLocation = shouldThrottleRequests
                         || (isForegroundOnlyMode && !record.mIsForegroundUid);
-                if (!provider.isUseableLocked() || isBatterySaverDisablingLocation) {
+                if (!manager.isUseable() || isBatterySaverDisablingLocation) {
                     if (isSettingsExemptLocked(record)) {
-                        providerRequest.locationSettingsIgnored = true;
-                        providerRequest.lowPowerMode = false;
+                        providerRequest.setLocationSettingsIgnored(true);
+                        providerRequest.setLowPowerMode(false);
                     } else {
                         continue;
                     }
@@ -1801,7 +1701,7 @@
 
 
                 // if we're forcing location, don't apply any throttling
-                if (!providerRequest.locationSettingsIgnored && !isThrottlingExemptLocked(
+                if (!providerRequest.isLocationSettingsIgnored() && !isThrottlingExemptLocked(
                         record.mReceiver.mCallerIdentity)) {
                     if (!record.mIsForegroundUid) {
                         interval = Math.max(interval, backgroundThrottleInterval);
@@ -1813,42 +1713,44 @@
                 }
 
                 record.mRequest = locationRequest;
-                providerRequest.locationRequests.add(locationRequest);
+                requests.add(locationRequest);
                 if (!locationRequest.isLowPowerMode()) {
-                    providerRequest.lowPowerMode = false;
+                    providerRequest.setLowPowerMode(false);
                 }
-                if (interval < providerRequest.interval) {
-                    providerRequest.reportLocation = true;
-                    providerRequest.interval = interval;
+                if (interval < providerRequest.getInterval()) {
+                    providerRequest.setInterval(interval);
                 }
             }
 
-            if (providerRequest.reportLocation) {
+            providerRequest.setLocationRequests(requests);
+
+            if (providerRequest.getInterval() < Long.MAX_VALUE) {
                 // calculate who to blame for power
                 // This is somewhat arbitrary. We pick a threshold interval
                 // that is slightly higher that the minimum interval, and
                 // spread the blame across all applications with a request
                 // under that threshold.
-                long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2;
+                // TODO: overflow
+                long thresholdInterval = (providerRequest.getInterval() + 1000) * 3 / 2;
                 for (UpdateRecord record : records) {
-                    if (isCurrentProfileLocked(
+                    if (mUserInfoStore.isCurrentUserOrProfile(
                             UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
                         LocationRequest locationRequest = record.mRequest;
 
                         // Don't assign battery blame for update records whose
                         // client has no permission to receive location data.
-                        if (!providerRequest.locationRequests.contains(locationRequest)) {
+                        if (!providerRequest.getLocationRequests().contains(locationRequest)) {
                             continue;
                         }
 
                         if (locationRequest.getInterval() <= thresholdInterval) {
                             if (record.mReceiver.mWorkSource != null
                                     && isValidWorkSource(record.mReceiver.mWorkSource)) {
-                                worksource.add(record.mReceiver.mWorkSource);
+                                providerRequest.getWorkSource().add(record.mReceiver.mWorkSource);
                             } else {
                                 // Assign blame to caller if there's no WorkSource associated with
                                 // the request or if it's invalid.
-                                worksource.add(
+                                providerRequest.getWorkSource().add(
                                         record.mReceiver.mCallerIdentity.mUid,
                                         record.mReceiver.mCallerIdentity.mPackageName);
                             }
@@ -1858,7 +1760,7 @@
             }
         }
 
-        provider.setRequest(providerRequest, worksource);
+        manager.setRequest(providerRequest.build());
     }
 
     /**
@@ -2198,8 +2100,8 @@
             throw new IllegalArgumentException("provider name must not be null");
         }
 
-        LocationProviderManager provider = getLocationProviderLocked(name);
-        if (provider == null) {
+        LocationProviderManager manager = getLocationProviderManager(name);
+        if (manager == null) {
             throw new IllegalArgumentException("provider doesn't exist: " + name);
         }
 
@@ -2217,7 +2119,7 @@
             oldRecord.disposeLocked(false);
         }
 
-        if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) {
+        if (!manager.isUseable() && !isSettingsExemptLocked(record)) {
             // Notify the listener that updates are currently disabled - but only if the request
             // does not ignore location settings
             receiver.callProviderEnabledLocked(name, false);
@@ -2320,16 +2222,16 @@
                 // or use the fused provider
                 String name = request.getProvider();
                 if (name == null) name = LocationManager.FUSED_PROVIDER;
-                LocationProviderManager provider = getLocationProviderLocked(name);
-                if (provider == null) return null;
+                LocationProviderManager manager = getLocationProviderManager(name);
+                if (manager == null) return null;
 
                 // only the current user or location providers may get location this way
-                if (!isCurrentProfileLocked(UserHandle.getUserId(uid)) && !isProviderPackage(
-                        packageName)) {
+                if (!mUserInfoStore.isCurrentUserOrProfile(UserHandle.getUserId(uid))
+                        && !isProviderPackage(packageName)) {
                     return null;
                 }
 
-                if (!provider.isUseableLocked()) {
+                if (!manager.isUseable()) {
                     return null;
                 }
 
@@ -2450,19 +2352,19 @@
                 "Access Fine Location permission not granted to inject Location");
 
         synchronized (mLock) {
-            LocationProviderManager provider = getLocationProviderLocked(location.getProvider());
-            if (provider == null || !provider.isUseableLocked()) {
+            LocationProviderManager manager = getLocationProviderManager(location.getProvider());
+            if (manager == null || !manager.isUseable()) {
                 return false;
             }
 
             // NOTE: If last location is already available, location is not injected.  If
             // provider's normal source (like a GPS chipset) have already provided an output
             // there is no need to inject this location.
-            if (mLastLocation.get(provider.getName()) != null) {
+            if (mLastLocation.get(manager.getName()) != null) {
                 return false;
             }
 
-            updateLastLocationLocked(location, provider.getName());
+            updateLastLocationLocked(location, manager.getName());
             return true;
         }
     }
@@ -2511,7 +2413,7 @@
                         packageName,
                         request,
                         /* hasListener= */ false,
-                        intent != null,
+                        true,
                         geofence,
                         mActivityManager.getPackageImportance(packageName));
             }
@@ -2542,7 +2444,7 @@
                         packageName,
                         /* LocationRequest= */ null,
                         /* hasListener= */ false,
-                        intent != null,
+                        true,
                         geofence,
                         mActivityManager.getPackageImportance(packageName));
             }
@@ -2555,7 +2457,7 @@
     @Override
     public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName,
             String featureId) {
-        return mGnssManagerService == null ? false : mGnssManagerService.registerGnssStatusCallback(
+        return mGnssManagerService != null && mGnssManagerService.registerGnssStatusCallback(
                 listener, packageName, featureId);
     }
 
@@ -2569,9 +2471,8 @@
             String packageName, String featureId, String listenerIdentifier) {
         Objects.requireNonNull(listenerIdentifier);
 
-        return mGnssManagerService == null ? false
-                : mGnssManagerService.addGnssMeasurementsListener(listener, packageName, featureId,
-                       listenerIdentifier);
+        return mGnssManagerService != null && mGnssManagerService.addGnssMeasurementsListener(
+                listener, packageName, featureId, listenerIdentifier);
     }
 
     @Override
@@ -2586,8 +2487,8 @@
     public void injectGnssMeasurementCorrections(
             GnssMeasurementCorrections measurementCorrections, String packageName) {
         if (mGnssManagerService != null) {
-            mGnssManagerService.injectGnssMeasurementCorrections(
-                    measurementCorrections, packageName);
+            mGnssManagerService.injectGnssMeasurementCorrections(measurementCorrections,
+                    packageName);
         }
     }
 
@@ -2602,9 +2503,8 @@
             String packageName, String featureId, String listenerIdentifier) {
         Objects.requireNonNull(listenerIdentifier);
 
-        return mGnssManagerService == null ? false
-                : mGnssManagerService.addGnssNavigationMessageListener(listener, packageName,
-                        featureId, listenerIdentifier);
+        return mGnssManagerService != null && mGnssManagerService.addGnssNavigationMessageListener(
+                listener, packageName, featureId, listenerIdentifier);
     }
 
     @Override
@@ -2634,9 +2534,10 @@
                     LocationStatsEnums.API_SEND_EXTRA_COMMAND,
                     providerName);
 
-            LocationProviderManager provider = getLocationProviderLocked(providerName);
-            if (provider != null) {
-                provider.sendExtraCommand(command, extras);
+            LocationProviderManager manager = getLocationProviderManager(providerName);
+            if (manager != null) {
+                manager.sendExtraCommand(Binder.getCallingUid(), Binder.getCallingPid(), command,
+                        extras);
             }
 
             mLocationUsageLogger.logLocationApiUsage(
@@ -2650,43 +2551,37 @@
 
     @Override
     public boolean sendNiResponse(int notifId, int userResponse) {
-        return mGnssManagerService == null ? false : mGnssManagerService.sendNiResponse(notifId,
+        return mGnssManagerService != null && mGnssManagerService.sendNiResponse(notifId,
                 userResponse);
     }
 
     @Override
     public ProviderProperties getProviderProperties(String providerName) {
-        synchronized (mLock) {
-            LocationProviderManager provider = getLocationProviderLocked(providerName);
-            if (provider == null) {
-                return null;
-            }
-            return provider.getPropertiesLocked();
+        LocationProviderManager manager = getLocationProviderManager(providerName);
+        if (manager == null) {
+            return null;
         }
+        return manager.getProperties();
     }
 
     @Override
     public boolean isProviderPackage(String packageName) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG,
                 Manifest.permission.READ_DEVICE_CONFIG + " permission required");
-        synchronized (mLock) {
-            for (LocationProviderManager provider : mProviders) {
-                if (provider.getPackagesLocked().contains(packageName)) {
-                    return true;
-                }
+        for (LocationProviderManager manager : mProviderManagers) {
+            if (manager.getPackages().contains(packageName)) {
+                return true;
             }
-            return false;
         }
+        return false;
     }
 
     @Override
     public List<String> getProviderPackages(String providerName) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG,
                 Manifest.permission.READ_DEVICE_CONFIG + " permission required");
-        synchronized (mLock) {
-            LocationProviderManager provider = getLocationProviderLocked(providerName);
-            return provider == null ? Collections.emptyList() : provider.getPackagesLocked();
-        }
+        LocationProviderManager manager = getLocationProviderManager(providerName);
+        return manager == null ? Collections.emptyList() : new ArrayList<>(manager.getPackages());
     }
 
     @Override
@@ -2753,8 +2648,8 @@
         if (FUSED_PROVIDER.equals(providerName)) return false;
 
         synchronized (mLock) {
-            LocationProviderManager provider = getLocationProviderLocked(providerName);
-            return provider != null && provider.isUseableLocked(userId);
+            LocationProviderManager manager = getLocationProviderManager(providerName);
+            return manager != null && manager.isUseable(userId);
         }
     }
 
@@ -2792,37 +2687,39 @@
     }
 
     @GuardedBy("mLock")
-    private void handleLocationChangedLocked(Location location, LocationProviderManager provider) {
-        if (!mProviders.contains(provider)) {
+    private void handleLocationChangedLocked(Location location, LocationProviderManager manager) {
+        if (!mProviderManagers.contains(manager)) {
+            Log.w(TAG, "received location from unknown provider: " + manager.getName());
             return;
         }
         if (!location.isComplete()) {
-            Log.w(TAG, "Dropping incomplete location: " + location);
+            Log.w(TAG, "dropping incomplete location from " + manager.getName() + " provider: "
+                    + location);
             return;
         }
 
-        // only notify passive provider and update last location for locations that come from
-        // useable providers
-        if (provider.isUseableLocked()) {
-            if (!provider.isPassiveLocked()) {
-                mPassiveProvider.updateLocation(location);
-            }
+        // notify passive provider
+        if (manager != mPassiveManager) {
+            mPassiveManager.updateLocation(new Location(location));
         }
 
         if (D) Log.d(TAG, "incoming location: " + location);
         long now = SystemClock.elapsedRealtime();
-        if (provider.isUseableLocked()) {
-            updateLastLocationLocked(location, provider.getName());
+
+
+        // only update last location for locations that come from useable providers
+        if (manager.isUseable()) {
+            updateLastLocationLocked(location, manager.getName());
         }
 
         // Update last known coarse interval location if enough time has passed.
         Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(
-                provider.getName());
+                manager.getName());
         if (lastLocationCoarseInterval == null) {
             lastLocationCoarseInterval = new Location(location);
 
-            if (provider.isUseableLocked()) {
-                mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval);
+            if (manager.isUseable()) {
+                mLastLocationCoarseInterval.put(manager.getName(), lastLocationCoarseInterval);
             }
         }
         long timeDeltaMs = TimeUnit.NANOSECONDS.toMillis(location.getElapsedRealtimeNanos()
@@ -2837,7 +2734,7 @@
                 lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
 
         // Skip if there are no UpdateRecords for this provider.
-        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
+        ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
         if (records == null || records.size() == 0) return;
 
         // Fetch coarse location
@@ -2854,17 +2751,16 @@
             Receiver receiver = r.mReceiver;
             boolean receiverDead = false;
 
-            if (!provider.isUseableLocked() && !isSettingsExemptLocked(r)) {
+            if (!manager.isUseable() && !isSettingsExemptLocked(r)) {
                 continue;
             }
 
             int receiverUserId = UserHandle.getUserId(receiver.mCallerIdentity.mUid);
-            if (!isCurrentProfileLocked(receiverUserId)
+            if (!mUserInfoStore.isCurrentUserOrProfile(receiverUserId)
                     && !isProviderPackage(receiver.mCallerIdentity.mPackageName)) {
                 if (D) {
                     Log.d(TAG, "skipping loc update for background user " + receiverUserId +
-                            " (current user: " + mCurrentUserId + ", app: " +
-                            receiver.mCallerIdentity.mPackageName + ")");
+                            " (app: " + receiver.mCallerIdentity.mPackageName + ")");
                 }
                 continue;
             }
@@ -2949,7 +2845,7 @@
             for (UpdateRecord r : deadUpdateRecords) {
                 r.disposeLocked(true);
             }
-            applyRequirementsLocked(provider);
+            applyRequirementsLocked(manager);
         }
     }
 
@@ -3006,143 +2902,99 @@
 
     // Mock Providers
 
-    private boolean canCallerAccessMockLocation(String opPackageName) {
-        return mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(),
-                opPackageName) == AppOpsManager.MODE_ALLOWED;
-    }
-
     @Override
-    public void addTestProvider(String name, ProviderProperties properties, String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
+    public void addTestProvider(String provider, ProviderProperties properties,
+            String packageName) {
+        if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+                != AppOpsManager.MODE_ALLOWED) {
             return;
         }
 
-        if (PASSIVE_PROVIDER.equals(name)) {
-            throw new IllegalArgumentException("Cannot mock the passive location provider");
+        synchronized (mLock) {
+            LocationProviderManager manager = getLocationProviderManager(provider);
+            if (manager == null) {
+                manager = new LocationProviderManager(provider);
+                mProviderManagers.add(manager);
+            }
+
+            manager.setMockProvider(new MockProvider(mContext, properties));
+        }
+    }
+
+    @Override
+    public void removeTestProvider(String provider, String packageName) {
+        if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+                != AppOpsManager.MODE_ALLOWED) {
+            return;
         }
 
         synchronized (mLock) {
-            long identity = Binder.clearCallingIdentity();
-            try {
-                LocationProviderManager oldProvider = getLocationProviderLocked(name);
-                if (oldProvider != null) {
-                    removeProviderLocked(oldProvider);
-                }
+            LocationProviderManager manager = getLocationProviderManager(provider);
+            if (manager == null) {
+                return;
+            }
 
-                MockLocationProvider mockProviderManager = new MockLocationProvider(name);
-                addProviderLocked(mockProviderManager);
-                mockProviderManager.attachLocked(
-                        new MockProvider(mContext, mockProviderManager, properties));
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+            manager.setMockProvider(null);
+            if (!manager.hasProvider()) {
+                mProviderManagers.remove(manager);
+                mLastLocation.remove(manager.getName());
+                mLastLocationCoarseInterval.remove(manager.getName());
             }
         }
     }
 
     @Override
-    public void removeTestProvider(String name, String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
+    public void setTestProviderLocation(String provider, Location location, String packageName) {
+        if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+                != AppOpsManager.MODE_ALLOWED) {
             return;
         }
 
-        synchronized (mLock) {
-            long identity = Binder.clearCallingIdentity();
-            try {
-                LocationProviderManager testProvider = getLocationProviderLocked(name);
-                if (testProvider == null || !testProvider.isMock()) {
-                    return;
-                }
-
-                removeProviderLocked(testProvider);
-
-                // reinstate real provider if available
-                LocationProviderManager realProvider = null;
-                for (LocationProviderManager provider : mRealProviders) {
-                    if (name.equals(provider.getName())) {
-                        realProvider = provider;
-                        break;
-                    }
-                }
-
-                if (realProvider != null) {
-                    addProviderLocked(realProvider);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
+        LocationProviderManager manager = getLocationProviderManager(provider);
+        if (manager == null) {
+            throw new IllegalArgumentException("provider doesn't exist: " + provider);
         }
+
+        manager.setMockProviderLocation(location);
     }
 
     @Override
-    public void setTestProviderLocation(String providerName, Location location,
-            String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
+    public void setTestProviderEnabled(String provider, boolean enabled, String packageName) {
+        if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+                != AppOpsManager.MODE_ALLOWED) {
             return;
         }
 
-        synchronized (mLock) {
-            LocationProviderManager testProvider = getLocationProviderLocked(providerName);
-            if (testProvider == null || !testProvider.isMock()) {
-                throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
-            }
-
-            String locationProvider = location.getProvider();
-            if (!TextUtils.isEmpty(locationProvider) && !providerName.equals(locationProvider)) {
-                // The location has an explicit provider that is different from the mock
-                // provider name. The caller may be trying to fool us via b/33091107.
-                EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
-                        providerName + "!=" + location.getProvider());
-            }
-
-            ((MockLocationProvider) testProvider).setLocationLocked(location);
-        }
-    }
-
-    @Override
-    public void setTestProviderEnabled(String providerName, boolean enabled, String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
-            return;
+        LocationProviderManager manager = getLocationProviderManager(provider);
+        if (manager == null) {
+            throw new IllegalArgumentException("provider doesn't exist: " + provider);
         }
 
-        synchronized (mLock) {
-            LocationProviderManager testProvider = getLocationProviderLocked(providerName);
-            if (testProvider == null || !testProvider.isMock()) {
-                throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
-            }
-
-            ((MockLocationProvider) testProvider).setEnabledLocked(enabled);
-        }
+        manager.setMockProviderEnabled(enabled);
     }
 
     @Override
     @NonNull
-    public List<LocationRequest> getTestProviderCurrentRequests(String providerName,
-            String opPackageName) {
-        if (!canCallerAccessMockLocation(opPackageName)) {
+    public List<LocationRequest> getTestProviderCurrentRequests(String provider,
+            String packageName) {
+        if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+                != AppOpsManager.MODE_ALLOWED) {
             return Collections.emptyList();
         }
 
-        synchronized (mLock) {
-            LocationProviderManager testProvider = getLocationProviderLocked(providerName);
-            if (testProvider == null || !testProvider.isMock()) {
-                throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
-            }
-
-            MockLocationProvider provider = (MockLocationProvider) testProvider;
-            if (provider.mCurrentRequest == null) {
-                return Collections.emptyList();
-            }
-            List<LocationRequest> requests = new ArrayList<>();
-            for (LocationRequest request : provider.mCurrentRequest.locationRequests) {
-                requests.add(new LocationRequest(request));
-            }
-            return requests;
+        LocationProviderManager manager = getLocationProviderManager(provider);
+        if (manager == null) {
+            throw new IllegalArgumentException("provider doesn't exist: " + provider);
         }
+
+        return manager.getMockProviderRequests();
     }
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
+            return;
+        }
 
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
 
@@ -3158,9 +3010,17 @@
                     + TimeUtils.logTimeOfDay(System.currentTimeMillis()));
             ipw.println(", Current Elapsed Time: "
                     + TimeUtils.formatDuration(SystemClock.elapsedRealtime()));
-            ipw.println("Current user: " + mCurrentUserId + " " + Arrays.toString(
-                    mCurrentUserProfiles));
-            ipw.println("Location Mode: " + isLocationEnabledForUser(mCurrentUserId));
+
+            ipw.println("User Info:");
+            ipw.increaseIndent();
+            mUserInfoStore.dump(fd, ipw, args);
+            ipw.decreaseIndent();
+
+            ipw.println("Location Settings:");
+            ipw.increaseIndent();
+            mSettingsStore.dump(fd, ipw, args);
+            ipw.decreaseIndent();
+
             ipw.println("Battery Saver Location Mode: "
                     + locationPowerSaveModeToString(mBatterySaverMode));
 
@@ -3192,6 +3052,8 @@
             }
             ipw.decreaseIndent();
 
+            mRequestStatistics.history.dump(ipw);
+
             ipw.println("Last Known Locations:");
             ipw.increaseIndent();
             for (Map.Entry<String, Location> entry : mLastLocation.entrySet()) {
@@ -3225,24 +3087,19 @@
                 ipw.decreaseIndent();
             }
 
-            ipw.println("Location Settings:");
-            ipw.increaseIndent();
-            mSettingsStore.dump(fd, ipw, args);
-            ipw.decreaseIndent();
-
             ipw.println("Location Providers:");
             ipw.increaseIndent();
-            for (LocationProviderManager provider : mProviders) {
-                provider.dumpLocked(fd, ipw, args);
+            for (LocationProviderManager manager : mProviderManagers) {
+                manager.dump(fd, ipw, args);
             }
             ipw.decreaseIndent();
-        }
 
-        if (mGnssManagerService != null) {
-            ipw.println("GNSS:");
-            ipw.increaseIndent();
-            mGnssManagerService.dump(fd, ipw, args);
-            ipw.decreaseIndent();
+            if (mGnssManagerService != null) {
+                ipw.println("GNSS:");
+                ipw.increaseIndent();
+                mGnssManagerService.dump(fd, ipw, args);
+                ipw.decreaseIndent();
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 1f73650..05d8360 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -58,10 +58,12 @@
 import android.net.NetworkStats;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
-import android.net.TetherConfigParcel;
 import android.net.TetherStatsParcel;
 import android.net.UidRange;
 import android.net.UidRangeParcel;
+import android.net.shared.NetdUtils;
+import android.net.shared.RouteUtils;
+import android.net.shared.RouteUtils.ModifyOperation;
 import android.net.util.NetdService;
 import android.os.BatteryStats;
 import android.os.Binder;
@@ -898,48 +900,14 @@
 
     @Override
     public void addRoute(int netId, RouteInfo route) {
-        modifyRoute(MODIFY_OPERATION_ADD, netId, route);
+        NetworkStack.checkNetworkStackPermission(mContext);
+        RouteUtils.modifyRoute(mNetdService, ModifyOperation.ADD, netId, route);
     }
 
     @Override
     public void removeRoute(int netId, RouteInfo route) {
-        modifyRoute(MODIFY_OPERATION_REMOVE, netId, route);
-    }
-
-    private void modifyRoute(boolean add, int netId, RouteInfo route) {
         NetworkStack.checkNetworkStackPermission(mContext);
-
-        final String ifName = route.getInterface();
-        final String dst = route.getDestination().toString();
-        final String nextHop;
-
-        switch (route.getType()) {
-            case RouteInfo.RTN_UNICAST:
-                if (route.hasGateway()) {
-                    nextHop = route.getGateway().getHostAddress();
-                } else {
-                    nextHop = INetd.NEXTHOP_NONE;
-                }
-                break;
-            case RouteInfo.RTN_UNREACHABLE:
-                nextHop = INetd.NEXTHOP_UNREACHABLE;
-                break;
-            case RouteInfo.RTN_THROW:
-                nextHop = INetd.NEXTHOP_THROW;
-                break;
-            default:
-                nextHop = INetd.NEXTHOP_NONE;
-                break;
-        }
-        try {
-            if (add) {
-                mNetdService.networkAddRoute(netId, ifName, dst, nextHop);
-            } else {
-                mNetdService.networkRemoveRoute(netId, ifName, dst, nextHop);
-            }
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
+        RouteUtils.modifyRoute(mNetdService, ModifyOperation.REMOVE, netId, route);
     }
 
     private ArrayList<String> readRouteList(String filename) {
@@ -1023,12 +991,8 @@
     @Override
     public void startTetheringWithConfiguration(boolean usingLegacyDnsProxy, String[] dhcpRange) {
         NetworkStack.checkNetworkStackPermission(mContext);
-        // an odd number of addrs will fail
         try {
-            final TetherConfigParcel config = new TetherConfigParcel();
-            config.usingLegacyDnsProxy = usingLegacyDnsProxy;
-            config.dhcpRanges = dhcpRange;
-            mNetdService.tetherStartWithConfiguration(config);
+            NetdUtils.tetherStart(mNetdService, usingLegacyDnsProxy, dhcpRange);
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
@@ -1060,26 +1024,21 @@
     public void tetherInterface(String iface) {
         NetworkStack.checkNetworkStackPermission(mContext);
         try {
-            mNetdService.tetherInterfaceAdd(iface);
+            final LinkAddress addr = getInterfaceConfig(iface).getLinkAddress();
+            final IpPrefix dest = new IpPrefix(addr.getAddress(), addr.getPrefixLength());
+            NetdUtils.tetherInterface(mNetdService, iface, dest);
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
         }
-        List<RouteInfo> routes = new ArrayList<>();
-        // The RouteInfo constructor truncates the LinkAddress to a network prefix, thus making it
-        // suitable to use as a route destination.
-        routes.add(new RouteInfo(getInterfaceConfig(iface).getLinkAddress(), null, iface));
-        addInterfaceToLocalNetwork(iface, routes);
     }
 
     @Override
     public void untetherInterface(String iface) {
         NetworkStack.checkNetworkStackPermission(mContext);
         try {
-            mNetdService.tetherInterfaceRemove(iface);
+            NetdUtils.untetherInterface(mNetdService, iface);
         } catch (RemoteException | ServiceSpecificException e) {
             throw new IllegalStateException(e);
-        } finally {
-            removeInterfaceFromLocalNetwork(iface);
         }
     }
 
@@ -2122,16 +2081,8 @@
     @Override
     public void addInterfaceToLocalNetwork(String iface, List<RouteInfo> routes) {
         modifyInterfaceInNetwork(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID, iface);
-
-        for (RouteInfo route : routes) {
-            if (!route.isDefaultRoute()) {
-                modifyRoute(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID, route);
-            }
-        }
-
-        // IPv6 link local should be activated always.
-        modifyRoute(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID,
-                new RouteInfo(new IpPrefix("fe80::/64"), null, iface));
+        // modifyInterfaceInNetwork already check calling permission.
+        RouteUtils.addRoutesToLocalNetwork(mNetdService, iface, routes);
     }
 
     @Override
@@ -2141,17 +2092,8 @@
 
     @Override
     public int removeRoutesFromLocalNetwork(List<RouteInfo> routes) {
-        int failures = 0;
-
-        for (RouteInfo route : routes) {
-            try {
-                modifyRoute(MODIFY_OPERATION_REMOVE, INetd.LOCAL_NET_ID, route);
-            } catch (IllegalStateException e) {
-                failures++;
-            }
-        }
-
-        return failures;
+        NetworkStack.checkNetworkStackPermission(mContext);
+        return RouteUtils.removeRoutesFromLocalNetwork(mNetdService, routes);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
index cfe56052..d20936c 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
@@ -36,11 +36,11 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.SystemClock;
+import android.os.TimestampedValue;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.NtpTrustedTime;
 import android.util.TimeUtils;
-import android.util.TimestampedValue;
 
 import com.android.internal.util.DumpUtils;
 
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index deff440..7b4fd37 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -199,13 +199,14 @@
         mSystemClock = clock;
         mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS;
         loadFromFile();
+        sPackageWatchdog = this;
     }
 
     /** Creates or gets singleton instance of PackageWatchdog. */
     public static PackageWatchdog getInstance(Context context) {
         synchronized (PackageWatchdog.class) {
             if (sPackageWatchdog == null) {
-                sPackageWatchdog = new PackageWatchdog(context);
+                new PackageWatchdog(context);
             }
             return sPackageWatchdog;
         }
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index fb1a962..3dafc64 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -18,8 +18,12 @@
 
 import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
 
+import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
 import android.os.Build;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -36,8 +40,12 @@
 import android.util.SparseArray;
 import android.util.StatsLog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.server.PackageWatchdog.FailureReasons;
+import com.android.server.PackageWatchdog.PackageHealthObserver;
+import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
 import com.android.server.am.SettingsToPropertiesMapper;
 import com.android.server.utils.FlagNamespaceUtils;
 
@@ -79,19 +87,30 @@
     @VisibleForTesting
     static final long BOOT_TRIGGER_WINDOW_MILLIS = 600 * DateUtils.SECOND_IN_MILLIS;
     @VisibleForTesting
-    static final long PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS = 30 * DateUtils.SECOND_IN_MILLIS;
-    @VisibleForTesting
     static final String TAG = "RescueParty";
 
+    private static final String NAME = "rescue-party-observer";
+
+
     private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
     private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start";
     private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
 
+    private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
+            | ApplicationInfo.FLAG_SYSTEM;
+
+
     /** Threshold for boot loops */
     private static final Threshold sBoot = new BootThreshold();
     /** Threshold for app crash loops */
     private static SparseArray<Threshold> sApps = new SparseArray<>();
 
+    /** Register the Rescue Party observer as a Package Watchdog health observer */
+    public static void registerHealthObserver(Context context) {
+        PackageWatchdog.getInstance(context).registerHealthObserver(
+                RescuePartyObserver.getInstance(context));
+    }
+
     private static boolean isDisabled() {
         // Check if we're explicitly enabled for testing
         if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) {
@@ -135,24 +154,6 @@
     }
 
     /**
-     * Take note of a persistent app or apex module crash. If we notice too many of these
-     * events happening in rapid succession, we'll send out a rescue party.
-     */
-    public static void noteAppCrash(Context context, int uid) {
-        if (isDisabled()) return;
-        Threshold t = sApps.get(uid);
-        if (t == null) {
-            t = new AppThreshold(uid);
-            sApps.put(uid, t);
-        }
-        if (t.incrementAndTest()) {
-            t.reset();
-            incrementRescueLevel(t.uid);
-            executeRescueLevel(context);
-        }
-    }
-
-    /**
      * Check if we're currently attempting to reboot for a factory reset.
      */
     public static boolean isAttemptingFactoryReset() {
@@ -171,11 +172,6 @@
     @VisibleForTesting
     static void resetAllThresholds() {
         sBoot.reset();
-
-        for (int i = 0; i < sApps.size(); i++) {
-            Threshold appThreshold = sApps.get(sApps.keyAt(i));
-            appThreshold.reset();
-        }
     }
 
     @VisibleForTesting
@@ -243,6 +239,28 @@
                 FlagNamespaceUtils.NAMESPACE_NO_PACKAGE);
     }
 
+    private static int mapRescueLevelToUserImpact(int rescueLevel) {
+        switch(rescueLevel) {
+            case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
+            case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
+                return PackageHealthObserverImpact.USER_IMPACT_LOW;
+            case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
+            case LEVEL_FACTORY_RESET:
+                return PackageHealthObserverImpact.USER_IMPACT_HIGH;
+            default:
+                return PackageHealthObserverImpact.USER_IMPACT_NONE;
+        }
+    }
+
+    private static int getPackageUid(Context context, String packageName) {
+        try {
+            return context.getPackageManager().getPackageUid(packageName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            // Since UIDs are always >= 0, this value means the UID could not be determined.
+            return -1;
+        }
+    }
+
     private static void resetAllSettings(Context context, int mode) throws Exception {
         // Try our best to reset all settings possible, and once finished
         // rethrow any exception that we encountered
@@ -271,6 +289,89 @@
     }
 
     /**
+     * Handle mitigation action for package failures. This observer will be register to Package
+     * Watchdog and will receive calls about package failures. This observer is persistent so it
+     * may choose to mitigate failures for packages it has not explicitly asked to observe.
+     */
+    public static class RescuePartyObserver implements PackageHealthObserver {
+
+        private Context mContext;
+
+        @GuardedBy("RescuePartyObserver.class")
+        static RescuePartyObserver sRescuePartyObserver;
+
+        private RescuePartyObserver(Context context) {
+            mContext = context;
+        }
+
+        /** Creates or gets singleton instance of RescueParty. */
+        public static RescuePartyObserver getInstance(Context context) {
+            synchronized (RescuePartyObserver.class) {
+                if (sRescuePartyObserver == null) {
+                    sRescuePartyObserver = new RescuePartyObserver(context);
+                }
+                return sRescuePartyObserver;
+            }
+        }
+
+        @Override
+        public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
+                @FailureReasons int failureReason) {
+            if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
+                    || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
+                int rescueLevel = MathUtils.constrain(
+                        SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) + 1,
+                        LEVEL_NONE, LEVEL_FACTORY_RESET);
+                return mapRescueLevelToUserImpact(rescueLevel);
+            } else {
+                return PackageHealthObserverImpact.USER_IMPACT_NONE;
+            }
+        }
+
+        @Override
+        public boolean execute(@Nullable VersionedPackage failedPackage,
+                @FailureReasons int failureReason) {
+            if (isDisabled()) {
+                return false;
+            }
+            if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
+                    || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
+                int triggerUid = getPackageUid(mContext, failedPackage.getPackageName());
+                incrementRescueLevel(triggerUid);
+                executeRescueLevel(mContext);
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        @Override
+        public boolean isPersistent() {
+            return true;
+        }
+
+        @Override
+        public boolean mayObservePackage(String packageName) {
+            PackageManager pm = mContext.getPackageManager();
+            try {
+                // A package is a Mainline module if this is non-null
+                if (pm.getModuleInfo(packageName, 0) != null) {
+                    return true;
+                }
+                ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+                return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
+            } catch (PackageManager.NameNotFoundException e) {
+                return false;
+            }
+        }
+
+        @Override
+        public String getName() {
+            return NAME;
+        }
+    }
+
+    /**
      * Threshold that can be triggered if a number of events occur within a
      * window of time.
      */
@@ -349,27 +450,6 @@
         }
     }
 
-    /**
-     * Specialization of {@link Threshold} for monitoring app crashes. It stores
-     * counters in memory.
-     */
-    private static class AppThreshold extends Threshold {
-        private int count;
-        private long start;
-
-        public AppThreshold(int uid) {
-            // We're interested in TRIGGER_COUNT events in any
-            // PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS second period; apps crash pretty quickly
-            // so we can keep a tight leash on them.
-            super(uid, TRIGGER_COUNT, PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS);
-        }
-
-        @Override public int getCount() { return count; }
-        @Override public void setCount(int count) { this.count = count; }
-        @Override public long getStart() { return start; }
-        @Override public void setStart(long start) { this.start = start; }
-    }
-
     private static int[] getAllUserIds() {
         int[] userIds = { UserHandle.USER_SYSTEM };
         try {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 3d455ee..db54214 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import static android.Manifest.permission.ACCESS_MTP;
 import static android.Manifest.permission.INSTALL_PACKAGES;
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
@@ -111,6 +112,7 @@
 import android.os.storage.VolumeInfo;
 import android.os.storage.VolumeRecord;
 import android.provider.DeviceConfig;
+import android.provider.Downloads;
 import android.provider.MediaStore;
 import android.provider.Settings;
 import android.sysprop.VoldProperties;
@@ -367,6 +369,8 @@
 
     private volatile int mMediaStoreAuthorityAppId = -1;
 
+    private volatile int mDownloadsAuthorityAppId = -1;
+
     private volatile int mCurrentUserId = UserHandle.USER_SYSTEM;
 
     private final Installer mInstaller;
@@ -1788,6 +1792,15 @@
             mMediaStoreAuthorityAppId = UserHandle.getAppId(provider.applicationInfo.uid);
         }
 
+        provider = mPmInternal.resolveContentProvider(
+                Downloads.Impl.AUTHORITY, PackageManager.MATCH_DIRECT_BOOT_AWARE
+                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+                UserHandle.getUserId(UserHandle.USER_SYSTEM));
+
+        if (provider != null) {
+            mDownloadsAuthorityAppId = UserHandle.getAppId(provider.applicationInfo.uid);
+        }
+
         try {
             mIAppOpsService.startWatchingMode(OP_REQUEST_INSTALL_PACKAGES, null, mAppOpsCallback);
             mIAppOpsService.startWatchingMode(OP_LEGACY_STORAGE, null, mAppOpsCallback);
@@ -3881,6 +3894,19 @@
                 return Zygote.MOUNT_EXTERNAL_PASS_THROUGH;
             }
 
+            if (mIsFuseEnabled && mDownloadsAuthorityAppId == UserHandle.getAppId(uid)) {
+                // DownloadManager can write in app-private directories on behalf of apps;
+                // give it write access to Android/
+                return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
+            }
+
+            final boolean hasMtp = mIPackageManager.checkUidPermission(ACCESS_MTP, uid) ==
+                    PERMISSION_GRANTED;
+            if (mIsFuseEnabled && hasMtp) {
+                // The process hosting the MTP server should be able to write in Android/
+                return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
+            }
+
             // Determine if caller is holding runtime permission
             final boolean hasRead = StorageManager.checkPermissionAndCheckOp(mContext, false, 0,
                     uid, packageName, READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE);
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index c5409f8..f46b9ae 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -18,18 +18,23 @@
 
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityThread;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.IBinder;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.os.UserManager;
 
 import com.android.server.pm.UserManagerService;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -57,15 +62,17 @@
  *
  * {@hide}
  */
+//@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
 public abstract class SystemService {
 
+    /** @hide */
     // TODO(b/133242016) STOPSHIP: change to false before R ships
     protected static final boolean DEBUG_USER = true;
 
     /*
-     * Boot Phases
+     * The earliest boot phase the system send to system services on boot.
      */
-    public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // maybe should be a dependency?
+    public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100;
 
     /**
      * After receiving this boot phase, services can obtain lock settings data.
@@ -98,13 +105,70 @@
      * After receiving this boot phase, services can allow user interaction with the device.
      * This phase occurs when boot has completed and the home application has started.
      * System services may prefer to listen to this phase rather than registering a
-     * broadcast receiver for ACTION_BOOT_COMPLETED to reduce overall latency.
+     * broadcast receiver for {@link android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED}
+     * to reduce overall latency.
      */
     public static final int PHASE_BOOT_COMPLETED = 1000;
 
+    /** @hide */
+    @IntDef(flag = true, prefix = { "PHASE_" }, value = {
+            PHASE_WAIT_FOR_DEFAULT_DISPLAY,
+            PHASE_LOCK_SETTINGS_READY,
+            PHASE_SYSTEM_SERVICES_READY,
+            PHASE_DEVICE_SPECIFIC_SERVICES_READY,
+            PHASE_ACTIVITY_MANAGER_READY,
+            PHASE_THIRD_PARTY_APPS_CAN_START,
+            PHASE_BOOT_COMPLETED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BootPhase {}
+
     private final Context mContext;
 
     /**
+     * Class representing user in question in the lifecycle callbacks.
+     * @hide
+     */
+    //@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
+    public static final class TargetUser {
+        @NonNull
+        private final UserInfo mUserInfo;
+
+        /** @hide */
+        public TargetUser(@NonNull UserInfo userInfo) {
+            mUserInfo = userInfo;
+        }
+
+        /**
+         * @return The information about the user. <b>NOTE: </b> this is a "live" object
+         * referenced by {@link UserManagerService} and hence should not be modified.
+         *
+         * @hide
+         */
+        @NonNull
+        public UserInfo getUserInfo() {
+            return mUserInfo;
+        }
+
+        /**
+         * @return the target {@link UserHandle}.
+         */
+        @NonNull
+        public UserHandle getUserHandle() {
+            return mUserInfo.getUserHandle();
+        }
+
+        /**
+         * @return the integer user id
+         *
+         * @hide
+         */
+        public int getUserIdentifier() {
+            return mUserInfo.id;
+        }
+    }
+
+    /**
      * Initializes the system service.
      * <p>
      * Subclasses must define a single argument constructor that accepts the context
@@ -113,13 +177,14 @@
      *
      * @param context The system server context.
      */
-    public SystemService(Context context) {
+    public SystemService(@NonNull Context context) {
         mContext = context;
     }
 
     /**
      * Gets the system context.
      */
+    @NonNull
     public final Context getContext() {
         return mContext;
     }
@@ -128,6 +193,8 @@
      * Get the system UI context. This context is to be used for displaying UI. It is themable,
      * which means resources can be overridden at runtime. Do not use to retrieve properties that
      * configure the behavior of the device that is not UX related.
+     *
+     * @hide
      */
     public final Context getUiContext() {
         // This has already been set up by the time any SystemServices are created.
@@ -137,15 +204,16 @@
     /**
      * Returns true if the system is running in safe mode.
      * TODO: we should define in which phase this becomes valid
+     *
+     * @hide
      */
     public final boolean isSafeMode() {
         return getManager().isSafeMode();
     }
 
     /**
-     * Called when the dependencies listed in the @Service class-annotation are available
-     * and after the chosen start phase.
-     * When this method returns, the service should be published.
+     * Called when the system service should publish a binder service using
+     * {@link #publishBinderService(String, IBinder).}
      */
     public abstract void onStart();
 
@@ -155,7 +223,7 @@
      *
      * @param phase The current boot phase.
      */
-    public void onBootPhase(int phase) {}
+    public void onBootPhase(@BootPhase int phase) {}
 
     /**
      * Checks if the service should be available for the given user.
@@ -163,12 +231,14 @@
      * <p>By default returns {@code true}, but subclasses should extend for optimization, if they
      * don't support some types (like headless system user).
      */
-    public boolean isSupported(@NonNull UserInfo userInfo) {
+    public boolean isSupportedUser(@NonNull TargetUser user) {
         return true;
     }
 
     /**
-     * Helper method used to dump which users are {@link #onStartUser(UserInfo) supported}.
+     * Helper method used to dump which users are {@link #onStartUser(TargetUser) supported}.
+     *
+     * @hide
      */
     protected void dumpSupportedUsers(@NonNull PrintWriter pw, @NonNull String prefix) {
         final List<UserInfo> allUsers = UserManager.get(mContext).getUsers();
@@ -187,34 +257,59 @@
     }
 
     /**
-     * @deprecated subclasses should extend {@link #onStartUser(UserInfo)} instead (which by default
-     * calls this method).
+     * @deprecated subclasses should extend {@link #onStartUser(TargetUser)} instead
+     * (which by default calls this method).
+     *
+     * @hide
      */
     @Deprecated
     public void onStartUser(@UserIdInt int userId) {}
 
     /**
-     * Called when a new user is starting, for system services to initialize any per-user
-     * state they maintain for running users.
+     * @deprecated subclasses should extend {@link #onStartUser(TargetUser)} instead
+     * (which by default calls this method).
      *
-     * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
-     * user.
-     *
-     * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
-     * referenced by {@link UserManagerService} and hence should not be modified.
+     * @hide
      */
+    @Deprecated
     public void onStartUser(@NonNull UserInfo userInfo) {
         onStartUser(userInfo.id);
     }
 
     /**
-     * @deprecated subclasses should extend {@link #onUnlockUser(UserInfo)} instead (which by
+     * Called when a new user is starting, for system services to initialize any per-user
+     * state they maintain for running users.
+     *
+     * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+     * this user.
+     *
+     * @param user target user
+     */
+    public void onStartUser(@NonNull TargetUser user) {
+        onStartUser(user.getUserInfo());
+    }
+
+    /**
+     * @deprecated subclasses should extend {@link #onUnlockUser(TargetUser)} instead (which by
      * default calls this method).
+     *
+     * @hide
      */
     @Deprecated
     public void onUnlockUser(@UserIdInt int userId) {}
 
     /**
+     * @deprecated subclasses should extend {@link #onUnlockUser(TargetUser)} instead (which by
+     * default calls this method).
+     *
+     * @hide
+     */
+    @Deprecated
+    public void onUnlockUser(@NonNull UserInfo userInfo) {
+        onUnlockUser(userInfo.id);
+    }
+
+    /**
      * Called when an existing user is in the process of being unlocked. This
      * means the credential-encrypted storage for that user is now available,
      * and encryption-aware component filtering is no longer in effect.
@@ -226,90 +321,127 @@
      * {@link UserManager#isUserUnlockingOrUnlocked(int)} to handle both of
      * these states.
      * <p>
-     * This method is only called when the service {@link #isSupported(UserInfo) supports} this
-     * user.
+     * This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+     * this user.
      *
-     * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
-     * referenced by {@link UserManagerService} and hence should not be modified.
+     * @param user target user
      */
-    public void onUnlockUser(@NonNull UserInfo userInfo) {
-        onUnlockUser(userInfo.id);
+    public void onUnlockUser(@NonNull TargetUser user) {
+        onUnlockUser(user.getUserInfo());
     }
 
     /**
-     * @deprecated subclasses should extend {@link #onSwitchUser(UserInfo, UserInfo)} instead
+     * @deprecated subclasses should extend {@link #onSwitchUser(TargetUser, TargetUser)} instead
      * (which by default calls this method).
+     *
+     * @hide
      */
     @Deprecated
-    public void onSwitchUser(@UserIdInt int userId) {}
+    public void onSwitchUser(@UserIdInt int toUserId) {}
+
+    /**
+     * @deprecated subclasses should extend {@link #onSwitchUser(TargetUser, TargetUser)} instead
+     * (which by default calls this method).
+     *
+     * @hide
+     */
+    @Deprecated
+    public void onSwitchUser(@Nullable UserInfo from, @NonNull UserInfo to) {
+        onSwitchUser(to.id);
+    }
 
     /**
      * Called when switching to a different foreground user, for system services that have
      * special behavior for whichever user is currently in the foreground.  This is called
      * before any application processes are aware of the new user.
      *
-     * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} either
-     * of the users ({@code from} or {@code to}).
+     * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+     * either of the users ({@code from} or {@code to}).
      *
      * <b>NOTE: </b> both {@code from} and {@code to} are "live" objects
      * referenced by {@link UserManagerService} and hence should not be modified.
      *
-     * @param from The information about the user being switched from.
-     * @param to The information about the user being switched from to.
+     * @param from the user switching from
+     * @param to the user switching to
      */
-    public void onSwitchUser(@NonNull UserInfo from, @NonNull UserInfo to) {
-        onSwitchUser(to.id);
+    public void onSwitchUser(@Nullable TargetUser from, @NonNull TargetUser to) {
+        onSwitchUser((from == null ? null : from.getUserInfo()), to.getUserInfo());
     }
 
     /**
-     * @deprecated subclasses should extend {@link #onStopUser(UserInfo)} instead (which by default
-     * calls this method).
+     * @deprecated subclasses should extend {@link #onStopUser(TargetUser)} instead
+     * (which by default calls this method).
+     *
+     * @hide
      */
     @Deprecated
     public void onStopUser(@UserIdInt int userId) {}
 
     /**
+     * @deprecated subclasses should extend {@link #onStopUser(TargetUser)} instead
+     * (which by default calls this method).
+     *
+     * @hide
+     */
+    @Deprecated
+    public void onStopUser(@NonNull UserInfo user) {
+        onStopUser(user.id);
+
+    }
+
+    /**
      * Called when an existing user is stopping, for system services to finalize any per-user
      * state they maintain for running users.  This is called prior to sending the SHUTDOWN
      * broadcast to the user; it is a good place to stop making use of any resources of that
      * user (such as binding to a service running in the user).
      *
-     * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
-     * user.
+     * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+     * this user.
      *
      * <p>NOTE: This is the last callback where the callee may access the target user's CE storage.
      *
-     * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
-     * referenced by {@link UserManagerService} and hence should not be modified.
+     * @param user target user
      */
-    public void onStopUser(@NonNull UserInfo userInfo) {
-        onStopUser(userInfo.id);
+    public void onStopUser(@NonNull TargetUser user) {
+        onStopUser(user.getUserInfo());
     }
 
     /**
-     * @deprecated subclasses should extend {@link #onCleanupUser(UserInfo)} instead (which by
+     * @deprecated subclasses should extend {@link #onCleanupUser(TargetUser)} instead (which by
      * default calls this method).
+     *
+     * @hide
      */
     @Deprecated
     public void onCleanupUser(@UserIdInt int userId) {}
 
     /**
+     * @deprecated subclasses should extend {@link #onCleanupUser(TargetUser)} instead (which by
+     * default calls this method).
+     *
+     * @hide
+     */
+    @Deprecated
+    public void onCleanupUser(@NonNull UserInfo user) {
+        onCleanupUser(user.id);
+    }
+
+    /**
      * Called when an existing user is stopping, for system services to finalize any per-user
      * state they maintain for running users.  This is called after all application process
      * teardown of the user is complete.
      *
-     * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
-     * user.
+     * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+     * this user.
      *
      * <p>NOTE: When this callback is called, the CE storage for the target user may not be
-     * accessible already.  Use {@link #onStopUser(UserInfo)} instead if you need to access the CE
+     * accessible already.  Use {@link #onStopUser(TargetUser)} instead if you need to access the CE
      * storage.
      *
-     * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
-     * referenced by {@link UserManagerService} and hence should not be modified.
+     * @param user target user
      */
-    public void onCleanupUser(@NonNull UserInfo userInfo) {
-        onCleanupUser(userInfo.id);
+    public void onCleanupUser(@NonNull TargetUser user) {
+        onCleanupUser(user.getUserInfo());
     }
 
     /**
@@ -318,7 +450,7 @@
      * @param name the name of the new service
      * @param service the service object
      */
-    protected final void publishBinderService(String name, IBinder service) {
+    protected final void publishBinderService(@NonNull String name, @NonNull IBinder service) {
         publishBinderService(name, service, false);
     }
 
@@ -330,7 +462,7 @@
      * @param allowIsolated set to true to allow isolated sandboxed processes
      * to access this service
      */
-    protected final void publishBinderService(String name, IBinder service,
+    protected final void publishBinderService(@NonNull String name, @NonNull IBinder service,
             boolean allowIsolated) {
         publishBinderService(name, service, allowIsolated, DUMP_FLAG_PRIORITY_DEFAULT);
     }
@@ -343,6 +475,8 @@
      * @param allowIsolated set to true to allow isolated sandboxed processes
      * to access this service
      * @param dumpPriority supported dump priority levels as a bitmask
+     *
+     * @hide
      */
     protected final void publishBinderService(String name, IBinder service,
             boolean allowIsolated, int dumpPriority) {
@@ -351,6 +485,8 @@
 
     /**
      * Get a binder service by its name.
+     *
+     * @hide
      */
     protected final IBinder getBinderService(String name) {
         return ServiceManager.getService(name);
@@ -358,6 +494,8 @@
 
     /**
      * Publish the service so it is only accessible to the system process.
+     *
+     * @hide
      */
     protected final <T> void publishLocalService(Class<T> type, T service) {
         LocalServices.addService(type, service);
@@ -365,6 +503,8 @@
 
     /**
      * Get a local service by interface.
+     *
+     * @hide
      */
     protected final <T> T getLocalService(Class<T> type) {
         return LocalServices.getService(type);
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index c715798..c0f43a8 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -27,6 +27,7 @@
 import android.os.UserManagerInternal;
 import android.util.Slog;
 
+import com.android.server.SystemService.TargetUser;
 import com.android.server.utils.TimingsTraceAndSlog;
 
 import java.io.File;
@@ -264,26 +265,26 @@
             @UserIdInt int curUserId, @UserIdInt int prevUserId) {
         t.traceBegin("ssm." + onWhat + "User-" + curUserId);
         Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId);
-        final UserInfo curUserInfo = getUserInfo(curUserId);
-        final UserInfo prevUserInfo = prevUserId == UserHandle.USER_NULL ? null
-                : getUserInfo(prevUserId);
+        final TargetUser curUser = new TargetUser(getUserInfo(curUserId));
+        final TargetUser prevUser = prevUserId == UserHandle.USER_NULL ? null
+                : new TargetUser(getUserInfo(prevUserId));
         final int serviceLen = mServices.size();
         for (int i = 0; i < serviceLen; i++) {
             final SystemService service = mServices.get(i);
             final String serviceName = service.getClass().getName();
-            boolean supported = service.isSupported(curUserInfo);
+            boolean supported = service.isSupportedUser(curUser);
 
             // Must check if either curUser or prevUser is supported (for example, if switching from
             // unsupported to supported, we still need to notify the services)
-            if (!supported && prevUserInfo != null) {
-                supported = service.isSupported(prevUserInfo);
+            if (!supported && prevUser != null) {
+                supported = service.isSupportedUser(prevUser);
             }
 
             if (!supported) {
                 if (DEBUG) {
                     Slog.d(TAG, "Skipping " + onWhat + "User-" + curUserId + " on service "
                             + serviceName + " because it's not supported (curUser: "
-                            + curUserInfo + ", prevUser:" + prevUserInfo + ")");
+                            + curUser + ", prevUser:" + prevUser + ")");
                 } else {
                     Slog.i(TAG,  "Skipping " + onWhat + "User-" + curUserId + " on "
                             + serviceName);
@@ -295,25 +296,25 @@
             try {
                 switch (onWhat) {
                     case SWITCH:
-                        service.onSwitchUser(prevUserInfo, curUserInfo);
+                        service.onSwitchUser(prevUser, curUser);
                         break;
                     case START:
-                        service.onStartUser(curUserInfo);
+                        service.onStartUser(curUser);
                         break;
                     case UNLOCK:
-                        service.onUnlockUser(curUserInfo);
+                        service.onUnlockUser(curUser);
                         break;
                     case STOP:
-                        service.onStopUser(curUserInfo);
+                        service.onStopUser(curUser);
                         break;
                     case CLEANUP:
-                        service.onCleanupUser(curUserInfo);
+                        service.onCleanupUser(curUser);
                         break;
                     default:
                         throw new IllegalArgumentException(onWhat + " what?");
                 }
             } catch (Exception ex) {
-                Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUserInfo
+                Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUser
                         + " to service " + serviceName, ex);
             }
             warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 3e6ccb5..2b7745b 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -83,7 +83,6 @@
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.IPhoneStateListener;
 import com.android.internal.telephony.ITelephonyRegistry;
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
@@ -2194,6 +2193,17 @@
     private static final String PHONE_CONSTANTS_STATE_KEY = "state";
     private static final String PHONE_CONSTANTS_SUBSCRIPTION_KEY = "subscription";
 
+    /**
+     * Broadcast Action: The phone's signal strength has changed. The intent will have the
+     * following extra values:
+     *   phoneName - A string version of the phone name.
+     *   asu - A numeric value for the signal strength.
+     *         An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu).
+     *         The following special values are defined:
+     *         0 means "-113 dBm or less".31 means "-51 dBm or greater".
+     */
+    public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR";
+
     private void broadcastServiceStateChanged(ServiceState state, int phoneId, int subId) {
         long ident = Binder.clearCallingIdentity();
         try {
@@ -2228,7 +2238,7 @@
             Binder.restoreCallingIdentity(ident);
         }
 
-        Intent intent = new Intent(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED);
+        Intent intent = new Intent(ACTION_SIGNAL_STRENGTH_CHANGED);
         Bundle data = new Bundle();
         fillInSignalStrengthNotifierBundle(signalStrength, data);
         intent.putExtras(data);
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 2091c2a..9ffe89c 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -1059,8 +1059,8 @@
             if (Sandman.shouldStartDockApp(getContext(), homeIntent)) {
                 try {
                     int result = ActivityTaskManager.getService().startActivityWithConfig(
-                            null, null, homeIntent, null, null, null, 0, 0,
-                            mConfiguration, null, UserHandle.USER_CURRENT);
+                            null, getContext().getBasePackageName(), homeIntent, null, null, null,
+                            0, 0, mConfiguration, null, UserHandle.USER_CURRENT);
                     if (ActivityManager.isStartResultSuccessful(result)) {
                         dockAppStarted = true;
                     } else if (result != ActivityManager.START_INTENT_NOT_RESOLVED) {
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 454941c..a60b09f 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -39,8 +39,10 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.StatsLog;
 
+import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.ZygoteConnectionConstants;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.wm.SurfaceAnimationThread;
@@ -606,13 +608,18 @@
             pids.add(Process.myPid());
             if (mPhonePid > 0) pids.add(mPhonePid);
 
+            long anrTime = SystemClock.uptimeMillis();
+            ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(false);
             final File stack = ActivityManagerService.dumpStackTraces(
-                    pids, null, null, getInterestingNativePids());
+                    pids, processCpuTracker, new SparseArray<>(), getInterestingNativePids());
 
             // Give some extra time to make sure the stack traces get written.
             // The system's been hanging for a minute, another second or two won't hurt much.
             SystemClock.sleep(5000);
 
+            processCpuTracker.update();
+            String cpuInfo = processCpuTracker.printCurrentState(anrTime);
+
             // Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the kernel log
             doSysRq('w');
             doSysRq('l');
@@ -627,7 +634,7 @@
                         if (mActivity != null) {
                             mActivity.addErrorToDropBox(
                                     "watchdog", null, "system_server", null, null, null,
-                                    subject, null, stack, null);
+                                    subject, cpuInfo, stack, null);
                         }
                         StatsLog.write(StatsLog.SYSTEM_SERVER_WATCHDOG_OCCURRED, subject);
                     }
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index 4b48ef9..143474b 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -413,6 +413,11 @@
                 case MESSAGE_ADB_CLEAR: {
                     Slog.d(TAG, "Received a request to clear the adb authorizations");
                     mConnectedKeys.clear();
+                    // If the key store has not yet been instantiated then do so now; this avoids
+                    // the unnecessary creation of the key store when adb is not enabled.
+                    if (mAdbKeyStore == null) {
+                        mAdbKeyStore = new AdbKeyStore();
+                    }
                     mAdbKeyStore.deleteKeyStore();
                     cancelJobToUpdateAdbKeyStore();
                     break;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index a58bd9b..e2a036a 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2068,7 +2068,7 @@
                 + " type=" + resolvedType + " callingUid=" + callingUid);
 
         userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
-                ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE, "service",
+                ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE, "service",
                 callingPackage);
 
         ServiceMap smap = getServiceMapLocked(userId);
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index a4c44fa..d7ad1c2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -2934,33 +2934,37 @@
         }
         ArraySet<Long> enabled = new ArraySet<>();
         ArraySet<Long> disabled = new ArraySet<>();
-        switch (toggleValue) {
-            case "enable":
-                enabled.add(changeId);
-                pw.println("Enabled change " + changeId + " for " + packageName + ".");
-                CompatibilityChangeConfig overrides =
-                        new CompatibilityChangeConfig(
-                                new Compatibility.ChangeConfig(enabled, disabled));
-                platformCompat.setOverrides(overrides, packageName);
-                return 0;
-            case "disable":
-                disabled.add(changeId);
-                pw.println("Disabled change " + changeId + " for " + packageName + ".");
-                overrides =
-                        new CompatibilityChangeConfig(
-                                new Compatibility.ChangeConfig(enabled, disabled));
-                platformCompat.setOverrides(overrides, packageName);
-                return 0;
-            case "reset":
-                if (platformCompat.clearOverride(changeId, packageName)) {
-                    pw.println("Reset change " + changeId + " for " + packageName
-                            + " to default value.");
-                } else {
-                    pw.println("No override exists for changeId " + changeId + ".");
-                }
-                return 0;
-            default:
-                pw.println("Invalid toggle value: '" + toggleValue + "'.");
+        try {
+            switch (toggleValue) {
+                case "enable":
+                    enabled.add(changeId);
+                    CompatibilityChangeConfig overrides =
+                            new CompatibilityChangeConfig(
+                                    new Compatibility.ChangeConfig(enabled, disabled));
+                    platformCompat.setOverrides(overrides, packageName);
+                    pw.println("Enabled change " + changeId + " for " + packageName + ".");
+                    return 0;
+                case "disable":
+                    disabled.add(changeId);
+                    overrides =
+                            new CompatibilityChangeConfig(
+                                    new Compatibility.ChangeConfig(enabled, disabled));
+                    platformCompat.setOverrides(overrides, packageName);
+                    pw.println("Disabled change " + changeId + " for " + packageName + ".");
+                    return 0;
+                case "reset":
+                    if (platformCompat.clearOverride(changeId, packageName)) {
+                        pw.println("Reset change " + changeId + " for " + packageName
+                                + " to default value.");
+                    } else {
+                        pw.println("No override exists for changeId " + changeId + ".");
+                    }
+                    return 0;
+                default:
+                    pw.println("Invalid toggle value: '" + toggleValue + "'.");
+            }
+        } catch (SecurityException e) {
+            pw.println(e.getMessage());
         }
         return -1;
     }
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 5e48dcf..8071f52 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -33,8 +33,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.ModuleInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.net.Uri;
 import android.os.Binder;
@@ -56,7 +54,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.server.PackageWatchdog;
-import com.android.server.RescueParty;
 import com.android.server.wm.WindowProcessController;
 
 import java.io.FileDescriptor;
@@ -423,28 +420,6 @@
         }
 
         if (r != null) {
-            boolean isApexModule = false;
-            try {
-                for (String androidPackage : r.getPackageList()) {
-                    ModuleInfo moduleInfo = mContext.getPackageManager().getModuleInfo(
-                            androidPackage, /*flags=*/ 0);
-                    if (moduleInfo != null) {
-                        isApexModule = true;
-                        break;
-                    }
-                }
-            } catch (IllegalStateException | PackageManager.NameNotFoundException e) {
-                // Call to PackageManager#getModuleInfo() can result in NameNotFoundException or
-                // IllegalStateException. In case they are thrown, there isn't much we can do
-                // other than proceed with app crash handling.
-            }
-
-            if (r.isPersistent() || isApexModule) {
-                // If a persistent app or apex module is stuck in a crash loop, the device isn't
-                // very usable, so we want to consider sending out a rescue party.
-                RescueParty.noteAppCrash(mContext, r.uid);
-            }
-
             mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode(),
                     PackageWatchdog.FAILURE_REASON_APP_CRASH);
         }
diff --git a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
index ebfc2a0..549051d 100644
--- a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
@@ -32,6 +32,7 @@
 import android.graphics.RectF;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.os.UserManager;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -81,8 +82,15 @@
                     .setImageDrawable(drawable);
         }
 
-        ((TextView) view.findViewById(R.id.user_loading))
-                .setText(res.getString(R.string.car_loading_profile));
+        TextView msgView = view.findViewById(R.id.user_loading);
+        // TODO: use developer settings instead
+        if (Build.IS_DEBUGGABLE) {
+            // TODO: use specific string
+            msgView.setText(res.getString(R.string.car_loading_profile) + " user\n(from "
+                    + mOldUser.id + " to " + mNewUser.id + ")");
+        } else {
+            msgView.setText(res.getString(R.string.car_loading_profile));
+        }
         setView(view);
     }
 
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index a1e1f29..e11008c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -103,6 +103,7 @@
 import com.android.internal.util.MemInfoReader;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
+import com.android.server.SystemConfig;
 import com.android.server.Watchdog;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.dex.DexManager;
@@ -357,6 +358,8 @@
 
     private boolean mAppDataIsolationEnabled = false;
 
+    private ArrayList<String> mAppDataIsolationWhitelistedApps;
+
     /**
      * Temporary to avoid allocations.  Protected by main lock.
      */
@@ -645,6 +648,9 @@
         // want some apps enabled while some apps disabled
         mAppDataIsolationEnabled =
                 SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+        mAppDataIsolationWhitelistedApps = new ArrayList<>(
+                SystemConfig.getInstance().getAppDataIsolationWhitelistedApps());
+
 
         if (sKillHandler == null) {
             sKillThread = new ServiceThread(TAG + ":kill",
@@ -1555,20 +1561,26 @@
                 } catch (RemoteException e) {
                     throw e.rethrowAsRuntimeException();
                 }
-
+                int numGids = 3;
+                if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) {
+                    numGids++;
+                }
                 /*
                  * Add shared application and profile GIDs so applications can share some
                  * resources like shared libraries and access user-wide resources
                  */
                 if (ArrayUtils.isEmpty(permGids)) {
-                    gids = new int[3];
+                    gids = new int[numGids];
                 } else {
-                    gids = new int[permGids.length + 3];
-                    System.arraycopy(permGids, 0, gids, 3, permGids.length);
+                    gids = new int[permGids.length + numGids];
+                    System.arraycopy(permGids, 0, gids, numGids, permGids.length);
                 }
                 gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));
                 gids[1] = UserHandle.getCacheAppGid(UserHandle.getAppId(uid));
                 gids[2] = UserHandle.getUserGid(UserHandle.getUserId(uid));
+                if (numGids > 3) {
+                    gids[3] = Process.SDCARD_RW_GID;
+                }
 
                 // Replace any invalid GIDs
                 if (gids[0] == UserHandle.ERR_GID) gids[0] = gids[2];
@@ -1905,6 +1917,16 @@
                 result.put(packageName, Pair.create(volumeUuid, inode));
             }
         }
+        if (mAppDataIsolationWhitelistedApps != null) {
+            for (String packageName : mAppDataIsolationWhitelistedApps) {
+                String volumeUuid = pmInt.getPackage(packageName).getVolumeUuid();
+                long inode = pmInt.getCeDataInode(packageName, userId);
+                if (inode != 0) {
+                    result.put(packageName, Pair.create(volumeUuid, inode));
+                }
+            }
+        }
+
         return result;
     }
 
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index eb1ab38..2b20782 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -16,12 +16,14 @@
 
 package com.android.server.am;
 
+import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM;
 import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
 import static android.app.ActivityManager.USER_OP_IS_CURRENT;
 import static android.app.ActivityManager.USER_OP_SUCCESS;
+import static android.app.ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE;
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
@@ -1641,9 +1643,10 @@
 
         if (callingUid != 0 && callingUid != SYSTEM_UID) {
             final boolean allow;
+            final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, targetUserId);
             if (mInjector.isCallerRecents(callingUid)
                     && callingUserId == getCurrentUserId()
-                    && isSameProfileGroup(callingUserId, targetUserId)) {
+                    && isSameProfileGroup) {
                 // If the caller is Recents and it is running in the current user, we then allow it
                 // to access its profiles.
                 allow = true;
@@ -1654,6 +1657,9 @@
             } else if (allowMode == ALLOW_FULL_ONLY) {
                 // We require full access, sucks to be you.
                 allow = false;
+            } else if (canInteractWithAcrossProfilesPermission(
+                    allowMode, isSameProfileGroup, callingPid, callingUid)) {
+                allow = true;
             } else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
                     callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
                 // If the caller does not have either permission, they are always doomed.
@@ -1661,10 +1667,11 @@
             } else if (allowMode == ALLOW_NON_FULL) {
                 // We are blanket allowing non-full access, you lucky caller!
                 allow = true;
-            } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
+            } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE
+                        || allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
                 // We may or may not allow this depending on whether the two users are
                 // in the same profile.
-                allow = isSameProfileGroup(callingUserId, targetUserId);
+                allow = isSameProfileGroup;
             } else {
                 throw new IllegalArgumentException("Unknown mode: " + allowMode);
             }
@@ -1690,6 +1697,11 @@
                     if (allowMode != ALLOW_FULL_ONLY) {
                         builder.append(" or ");
                         builder.append(INTERACT_ACROSS_USERS);
+                        if (isSameProfileGroup
+                                && allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
+                            builder.append(" or ");
+                            builder.append(INTERACT_ACROSS_PROFILES);
+                        }
                     }
                     String msg = builder.toString();
                     Slog.w(TAG, msg);
@@ -1710,6 +1722,19 @@
         return targetUserId;
     }
 
+    private boolean canInteractWithAcrossProfilesPermission(
+            int allowMode, boolean isSameProfileGroup, int callingPid, int callingUid) {
+        if (allowMode != ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
+            return false;
+        }
+        if (!isSameProfileGroup) {
+            return false;
+        }
+        return mInjector.checkComponentPermission(
+                INTERACT_ACROSS_PROFILES, callingPid, callingUid, /*owningUid= */-1,
+                /*exported= */true) == PackageManager.PERMISSION_GRANTED;
+    }
+
     int unsafeConvertIncomingUser(@UserIdInt int userId) {
         return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
                 ? getCurrentUserId(): userId;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a7593c7..e3b761e 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1759,8 +1759,9 @@
                 ? opNames.toArray(new String[opNames.size()]) : null;
 
         // Must not hold the appops lock
-        mHistoricalRegistry.getHistoricalOps(uid, packageName, featureId, opNamesArray, filter,
-                beginTimeMillis, endTimeMillis, flags, callback);
+        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
+                mHistoricalRegistry, uid, packageName, featureId, opNamesArray, filter,
+                beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse());
     }
 
     @Override
@@ -1778,8 +1779,9 @@
                 ? opNames.toArray(new String[opNames.size()]) : null;
 
         // Must not hold the appops lock
-        mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, featureId, opNamesArray,
-                filter, beginTimeMillis, endTimeMillis, flags, callback);
+        mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
+                mHistoricalRegistry, uid, packageName, featureId, opNamesArray,
+                filter, beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index bc4dc8f..cd2272a 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1700,6 +1700,14 @@
         }
     }
 
+    /** @see AudioManager#getDevicesForAttributes(AudioAttributes) */
+    public @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes(
+            @NonNull AudioAttributes attributes) {
+        Objects.requireNonNull(attributes);
+        enforceModifyAudioRoutingPermission();
+        return AudioSystem.getDevicesForAttributes(attributes);
+    }
+
 
     /** @see AudioManager#adjustVolume(int, int) */
     public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index cf83dd6..f15d999 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -17,8 +17,10 @@
 package com.android.server.compat;
 
 import android.compat.Compatibility.ChangeConfig;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.os.Environment;
+import android.os.RemoteException;
 import android.text.TextUtils;
 import android.util.LongArray;
 import android.util.LongSparseArray;
@@ -26,8 +28,11 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.AndroidBuildClassifier;
 import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.compat.CompatibilityChangeInfo;
+import com.android.internal.compat.IOverrideValidator;
+import com.android.internal.compat.OverrideAllowedState;
 import com.android.server.compat.config.Change;
 import com.android.server.compat.config.XmlParser;
 
@@ -54,22 +59,14 @@
 
     private static final String TAG = "CompatConfig";
 
-    private static final CompatConfig sInstance = new CompatConfig().initConfigFromLib(
-            Environment.buildPath(
-                    Environment.getRootDirectory(), "etc", "compatconfig"));
-
     @GuardedBy("mChanges")
     private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>();
 
-    @VisibleForTesting
-    CompatConfig() {
-    }
+    private IOverrideValidator mOverrideValidator;
 
-    /**
-     * @return The static instance of this class to be used within the system server.
-     */
-    static CompatConfig get() {
-        return sInstance;
+    @VisibleForTesting
+    CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) {
+        mOverrideValidator = new OverrideValidatorImpl(androidBuildClassifier, context, this);
     }
 
     /**
@@ -159,8 +156,12 @@
      * @param enabled     If the change should be enabled or disabled.
      * @return {@code true} if the change existed before adding the override.
      */
-    boolean addOverride(long changeId, String packageName, boolean enabled) {
+    boolean addOverride(long changeId, String packageName, boolean enabled)
+            throws RemoteException, SecurityException {
         boolean alreadyKnown = true;
+        OverrideAllowedState allowedState =
+                mOverrideValidator.getOverrideAllowedState(changeId, packageName);
+        allowedState.enforce(changeId, packageName);
         synchronized (mChanges) {
             CompatChange c = mChanges.get(changeId);
             if (c == null) {
@@ -186,6 +187,20 @@
     }
 
     /**
+     * Returns the minimum sdk version for which this change should be enabled (or 0 if it is not
+     * target sdk gated).
+     */
+    int minTargetSdkForChangeId(long changeId) {
+        synchronized (mChanges) {
+            CompatChange c = mChanges.get(changeId);
+            if (c == null) {
+                return 0;
+            }
+            return c.getEnableAfterTargetSdk();
+        }
+    }
+
+    /**
      * Removes an override previously added via {@link #addOverride(long, String, boolean)}. This
      * restores the default behaviour for the given change and app, once any app processes have been
      * restarted.
@@ -194,34 +209,44 @@
      * @param packageName The app package name that was overridden.
      * @return {@code true} if an override existed;
      */
-    boolean removeOverride(long changeId, String packageName) {
+    boolean removeOverride(long changeId, String packageName)
+            throws RemoteException, SecurityException {
         boolean overrideExists = false;
         synchronized (mChanges) {
             CompatChange c = mChanges.get(changeId);
-            if (c != null) {
-                overrideExists = true;
-                c.removePackageOverride(packageName);
+            try {
+                if (c != null) {
+                    OverrideAllowedState allowedState =
+                            mOverrideValidator.getOverrideAllowedState(changeId, packageName);
+                    allowedState.enforce(changeId, packageName);
+                    overrideExists = true;
+                    c.removePackageOverride(packageName);
+                }
+            } catch (RemoteException e) {
+                // Should never occur, since validator is in the same process.
+                throw new RuntimeException("Unable to call override validator!", e);
             }
         }
         return overrideExists;
     }
 
     /**
-     * Overrides the enabled state for a given change and app. This method is intended to be used
-     * *only* for debugging purposes.
+     * Overrides the enabled state for a given change and app.
      *
      * <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
      *
      * @param overrides   list of overrides to default changes config.
      * @param packageName app for which the overrides will be applied.
      */
-    void addOverrides(CompatibilityChangeConfig overrides, String packageName) {
+    void addOverrides(CompatibilityChangeConfig overrides, String packageName)
+            throws RemoteException, SecurityException {
         synchronized (mChanges) {
             for (Long changeId : overrides.enabledChanges()) {
                 addOverride(changeId, packageName, true);
             }
             for (Long changeId : overrides.disabledChanges()) {
                 addOverride(changeId, packageName, false);
+
             }
         }
     }
@@ -235,10 +260,22 @@
      *
      * @param packageName The package for which the overrides should be purged.
      */
-    void removePackageOverrides(String packageName) {
+    void removePackageOverrides(String packageName) throws RemoteException, SecurityException {
         synchronized (mChanges) {
             for (int i = 0; i < mChanges.size(); ++i) {
-                mChanges.valueAt(i).removePackageOverride(packageName);
+                try {
+                    CompatChange change = mChanges.valueAt(i);
+                    OverrideAllowedState allowedState =
+                            mOverrideValidator.getOverrideAllowedState(change.getId(),
+                                                                       packageName);
+                    allowedState.enforce(change.getId(), packageName);
+                    if (change != null) {
+                        mChanges.valueAt(i).removePackageOverride(packageName);
+                    }
+                } catch (RemoteException e) {
+                    // Should never occur, since validator is in the same process.
+                    throw new RuntimeException("Unable to call override validator!", e);
+                }
             }
         }
     }
@@ -326,17 +363,23 @@
         }
     }
 
-    CompatConfig initConfigFromLib(File libraryDir) {
+    static CompatConfig create(AndroidBuildClassifier androidBuildClassifier, Context context) {
+        CompatConfig config = new CompatConfig(androidBuildClassifier, context);
+        config.initConfigFromLib(Environment.buildPath(
+                Environment.getRootDirectory(), "etc", "compatconfig"));
+        return config;
+    }
+
+    void initConfigFromLib(File libraryDir) {
         if (!libraryDir.exists() || !libraryDir.isDirectory()) {
             Slog.e(TAG, "No directory " + libraryDir + ", skipping");
-            return this;
+            return;
         }
         for (File f : libraryDir.listFiles()) {
             Slog.d(TAG, "Found a config file: " + f.getPath());
             //TODO(b/138222363): Handle duplicate ids across config files.
             readConfig(f);
         }
-        return this;
     }
 
     private void readConfig(File configFile) {
@@ -350,4 +393,7 @@
         }
     }
 
+    IOverrideValidator getOverrideValidator() {
+        return mOverrideValidator;
+    }
 }
diff --git a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
new file mode 100644
index 0000000..dfc0080
--- /dev/null
+++ b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
@@ -0,0 +1,94 @@
+/*
+ * 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.compat;
+
+import static com.android.internal.compat.OverrideAllowedState.ALLOWED;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_NON_TARGET_SDK;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_NOT_DEBUGGABLE;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_TARGET_SDK_TOO_HIGH;
+import static com.android.internal.compat.OverrideAllowedState.PACKAGE_DOES_NOT_EXIST;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.AndroidBuildClassifier;
+import com.android.internal.compat.IOverrideValidator;
+import com.android.internal.compat.OverrideAllowedState;
+
+/**
+ * Implementation of the policy for allowing compat change overrides.
+ */
+public class OverrideValidatorImpl extends IOverrideValidator.Stub {
+
+    private AndroidBuildClassifier mAndroidBuildClassifier;
+    private Context mContext;
+    private CompatConfig mCompatConfig;
+
+    @VisibleForTesting
+    OverrideValidatorImpl(AndroidBuildClassifier androidBuildClassifier,
+                          Context context, CompatConfig config) {
+        mAndroidBuildClassifier = androidBuildClassifier;
+        mContext = context;
+        mCompatConfig = config;
+    }
+
+    @Override
+    public OverrideAllowedState getOverrideAllowedState(long changeId, String packageName) {
+        boolean debuggableBuild = false;
+        boolean finalBuild = false;
+
+        debuggableBuild = mAndroidBuildClassifier.isDebuggableBuild();
+        finalBuild = mAndroidBuildClassifier.isFinalBuild();
+
+        // Allow any override for userdebug or eng builds.
+        if (debuggableBuild) {
+            return new OverrideAllowedState(ALLOWED, -1, -1);
+        }
+        PackageManager packageManager = mContext.getPackageManager();
+        if (packageManager == null) {
+            throw new IllegalStateException("No PackageManager!");
+        }
+        ApplicationInfo applicationInfo;
+        try {
+            applicationInfo = packageManager.getApplicationInfo(packageName, 0);
+        } catch (NameNotFoundException e) {
+            return new OverrideAllowedState(PACKAGE_DOES_NOT_EXIST, -1, -1);
+        }
+        int appTargetSdk = applicationInfo.targetSdkVersion;
+        // Only allow overriding debuggable apps.
+        if ((applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
+            return new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1);
+        }
+        int minTargetSdk = mCompatConfig.minTargetSdkForChangeId(changeId);
+        // Do not allow overriding non-target sdk gated changes on user builds
+        if (minTargetSdk == -1) {
+            return new OverrideAllowedState(DISABLED_NON_TARGET_SDK, appTargetSdk, minTargetSdk);
+        }
+        // Allow overriding any change for debuggable apps on non-final builds.
+        if (!finalBuild) {
+            return new OverrideAllowedState(ALLOWED, appTargetSdk, minTargetSdk);
+        }
+        // Only allow to opt-in for a targetSdk gated change.
+        if (applicationInfo.targetSdkVersion < minTargetSdk) {
+            return new OverrideAllowedState(ALLOWED, appTargetSdk, minTargetSdk);
+        }
+        return new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, appTargetSdk, minTargetSdk);
+    }
+}
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 6ec4b9f..bb8b12e 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -27,9 +27,12 @@
 import android.util.Slog;
 import android.util.StatsLog;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.AndroidBuildClassifier;
 import com.android.internal.compat.ChangeReporter;
 import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.compat.CompatibilityChangeInfo;
+import com.android.internal.compat.IOverrideValidator;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
@@ -46,11 +49,21 @@
 
     private final Context mContext;
     private final ChangeReporter mChangeReporter;
+    private final CompatConfig mCompatConfig;
 
     public PlatformCompat(Context context) {
         mContext = context;
         mChangeReporter = new ChangeReporter(
                 StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER);
+        mCompatConfig = CompatConfig.create(new AndroidBuildClassifier(), mContext);
+    }
+
+    @VisibleForTesting
+    PlatformCompat(Context context, CompatConfig compatConfig) {
+        mContext = context;
+        mChangeReporter = new ChangeReporter(
+                StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER);
+        mCompatConfig = compatConfig;
     }
 
     @Override
@@ -75,7 +88,7 @@
 
     @Override
     public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
-        if (CompatConfig.get().isChangeEnabled(changeId, appInfo)) {
+        if (mCompatConfig.isChangeEnabled(changeId, appInfo)) {
             reportChange(changeId, appInfo.uid,
                     StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED);
             return true;
@@ -122,57 +135,59 @@
      * otherwise.
      */
     public boolean registerListener(long changeId, CompatChange.ChangeListener listener) {
-        return CompatConfig.get().registerListener(changeId, listener);
+        return mCompatConfig.registerListener(changeId, listener);
     }
 
     @Override
-    public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
-        CompatConfig.get().addOverrides(overrides, packageName);
+    public void setOverrides(CompatibilityChangeConfig overrides, String packageName)
+            throws RemoteException, SecurityException {
+        mCompatConfig.addOverrides(overrides, packageName);
         killPackage(packageName);
     }
 
     @Override
-    public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) {
-        CompatConfig.get().addOverrides(overrides, packageName);
+    public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName)
+            throws RemoteException, SecurityException {
+        mCompatConfig.addOverrides(overrides, packageName);
     }
 
     @Override
-    public void clearOverrides(String packageName) {
-        CompatConfig config = CompatConfig.get();
-        config.removePackageOverrides(packageName);
+    public void clearOverrides(String packageName) throws RemoteException, SecurityException {
+        mCompatConfig.removePackageOverrides(packageName);
         killPackage(packageName);
     }
 
     @Override
-    public void clearOverridesForTest(String packageName) {
-        CompatConfig config = CompatConfig.get();
-        config.removePackageOverrides(packageName);
+    public void clearOverridesForTest(String packageName)
+            throws RemoteException, SecurityException {
+        mCompatConfig.removePackageOverrides(packageName);
     }
 
     @Override
-    public boolean clearOverride(long changeId, String packageName) {
-        boolean existed = CompatConfig.get().removeOverride(changeId, packageName);
+    public boolean clearOverride(long changeId, String packageName)
+            throws RemoteException, SecurityException {
+        boolean existed = mCompatConfig.removeOverride(changeId, packageName);
         killPackage(packageName);
         return existed;
     }
 
     @Override
     public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) {
-        return CompatConfig.get().getAppConfig(appInfo);
+        return mCompatConfig.getAppConfig(appInfo);
     }
 
     @Override
     public CompatibilityChangeInfo[] listAllChanges() {
-        return CompatConfig.get().dumpChanges();
+        return mCompatConfig.dumpChanges();
     }
 
     /**
      * Check whether the change is known to the compat config.
-     * @param changeId
+     *
      * @return {@code true} if the change is known.
      */
     public boolean isKnownChangeId(long changeId) {
-        return CompatConfig.get().isKnownChangeId(changeId);
+        return mCompatConfig.isKnownChangeId(changeId);
 
     }
 
@@ -182,11 +197,11 @@
      *
      * @param appInfo The app in question
      * @return A sorted long array of change IDs. We use a primitive array to minimize memory
-     *      footprint: Every app process will store this array statically so we aim to reduce
-     *      overhead as much as possible.
+     * footprint: Every app process will store this array statically so we aim to reduce
+     * overhead as much as possible.
      */
     public long[] getDisabledChanges(ApplicationInfo appInfo) {
-        return CompatConfig.get().getDisabledChanges(appInfo);
+        return mCompatConfig.getDisabledChanges(appInfo);
     }
 
     /**
@@ -196,18 +211,24 @@
      * @return The change ID, or {@code -1} if no change with that name exists.
      */
     public long lookupChangeId(String name) {
-        return CompatConfig.get().lookupChangeId(name);
+        return mCompatConfig.lookupChangeId(name);
     }
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
-        CompatConfig.get().dumpConfig(pw);
+        mCompatConfig.dumpConfig(pw);
+    }
+
+    @Override
+    public IOverrideValidator getOverrideValidator() {
+        return mCompatConfig.getOverrideValidator();
     }
 
     /**
      * Clears information stored about events reported on behalf of an app.
      * To be called once upon app start or end. A second call would be a no-op.
+     *
      * @param appInfo the app to reset
      */
     public void resetReporting(ApplicationInfo appInfo) {
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index aea6d8d..f636d67 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -116,7 +116,8 @@
                 && !lp.hasIpv4Address();
 
         // If the network tells us it doesn't use clat, respect that.
-        final boolean skip464xlat = (nai.netMisc() != null) && nai.netMisc().skip464xlat;
+        final boolean skip464xlat = (nai.netAgentConfig() != null)
+                && nai.netAgentConfig().skip464xlat;
 
         return supported && connected && isIpv6OnlyNetwork && !skip464xlat;
     }
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 5e085ca..c1ab551 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -23,9 +23,9 @@
 import android.net.INetworkMonitor;
 import android.net.LinkProperties;
 import android.net.Network;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
-import android.net.NetworkMisc;
 import android.net.NetworkMonitorManager;
 import android.net.NetworkRequest;
 import android.net.NetworkScore;
@@ -127,7 +127,7 @@
     // This should only be modified by ConnectivityService, via setNetworkCapabilities().
     // TODO: make this private with a getter.
     public NetworkCapabilities networkCapabilities;
-    public final NetworkMisc networkMisc;
+    public final NetworkAgentConfig networkAgentConfig;
     // Indicates if netd has been told to create this Network. From this point on the appropriate
     // routing rules are setup and routes are added so packets can begin flowing over the Network.
     // This is a sticky bit; once set it is never cleared.
@@ -261,7 +261,7 @@
 
     public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
             LinkProperties lp, NetworkCapabilities nc, @NonNull NetworkScore ns, Context context,
-            Handler handler, NetworkMisc misc, ConnectivityService connService, INetd netd,
+            Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd,
             IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber) {
         this.messenger = messenger;
         asyncChannel = ac;
@@ -274,7 +274,7 @@
         mConnService = connService;
         mContext = context;
         mHandler = handler;
-        networkMisc = misc;
+        networkAgentConfig = config;
         this.factorySerialNumber = factorySerialNumber;
     }
 
@@ -309,8 +309,8 @@
         return mConnService;
     }
 
-    public NetworkMisc netMisc() {
-        return networkMisc;
+    public NetworkAgentConfig netAgentConfig() {
+        return networkAgentConfig;
     }
 
     public Handler handler() {
@@ -487,7 +487,8 @@
         // selected and we're trying to see what its score could be. This ensures that we don't tear
         // down an explicitly selected network before the user gets a chance to prefer it when
         // a higher-scoring network (e.g., Ethernet) is available.
-        if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
+        if (networkAgentConfig.explicitlySelected
+                && (networkAgentConfig.acceptUnvalidated || pretendValidated)) {
             return ConnectivityConstants.EXPLICITLY_SELECTED_NETWORK_SCORE;
         }
 
@@ -533,7 +534,8 @@
         synchronized (this) {
             // Network objects are outwardly immutable so there is no point in duplicating.
             // Duplicating also precludes sharing socket factories and connection pools.
-            final String subscriberId = (networkMisc != null) ? networkMisc.subscriberId : null;
+            final String subscriberId = (networkAgentConfig != null)
+                    ? networkAgentConfig.subscriberId : null;
             return new NetworkState(new NetworkInfo(networkInfo),
                     new LinkProperties(linkProperties),
                     new NetworkCapabilities(networkCapabilities), network, subscriberId, null);
@@ -641,13 +643,13 @@
                 + "nc{" + networkCapabilities + "}  Score{" + getCurrentScore() + "}  "
                 + "everValidated{" + everValidated + "}  lastValidated{" + lastValidated + "}  "
                 + "created{" + created + "} lingering{" + isLingering() + "} "
-                + "explicitlySelected{" + networkMisc.explicitlySelected + "} "
-                + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} "
+                + "explicitlySelected{" + networkAgentConfig.explicitlySelected + "} "
+                + "acceptUnvalidated{" + networkAgentConfig.acceptUnvalidated + "} "
                 + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} "
                 + "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} "
                 + "captivePortalValidationPending{" + captivePortalValidationPending + "} "
                 + "partialConnectivity{" + partialConnectivity + "} "
-                + "acceptPartialConnectivity{" + networkMisc.acceptPartialConnectivity + "} "
+                + "acceptPartialConnectivity{" + networkAgentConfig.acceptPartialConnectivity + "} "
                 + "clat{" + clatd + "} "
                 + "}";
     }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 6b70e5f..476f3f8 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -53,11 +53,11 @@
 import android.net.LocalSocketAddress;
 import android.net.Network;
 import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
 import android.net.RouteInfo;
 import android.net.UidRange;
 import android.net.VpnService;
@@ -914,7 +914,7 @@
      * has certain changes, in which case this method would just return {@code false}.
      */
     private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) {
-        // NetworkMisc cannot be updated without registering a new NetworkAgent.
+        // NetworkAgentConfig cannot be updated without registering a new NetworkAgent.
         if (oldConfig.allowBypass != mConfig.allowBypass) {
             Log.i(TAG, "Handover not possible due to changes to allowBypass");
             return false;
@@ -947,8 +947,8 @@
 
         mNetworkInfo.setDetailedState(DetailedState.CONNECTING, null, null);
 
-        NetworkMisc networkMisc = new NetworkMisc();
-        networkMisc.allowBypass = mConfig.allowBypass && !mLockdown;
+        NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
+        networkAgentConfig.allowBypass = mConfig.allowBypass && !mLockdown;
 
         mNetworkCapabilities.setEstablishingVpnAppUid(Binder.getCallingUid());
         mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserHandle,
@@ -957,8 +957,8 @@
         try {
             mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE /* logtag */,
                     mNetworkInfo, mNetworkCapabilities, lp,
-                    ConnectivityConstants.VPN_DEFAULT_SCORE, networkMisc,
-                    NetworkFactory.SerialNumber.VPN) {
+                    ConnectivityConstants.VPN_DEFAULT_SCORE, networkAgentConfig,
+                    NetworkProvider.ID_VPN) {
                             @Override
                             public void unwanted() {
                                 // We are user controlled, not driven by NetworkRequest.
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index ad728c1..8498dcb 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -237,15 +237,15 @@
                 lowestConsideredPriority++;
             }
 
-            int defaultModeId = defaultMode.getModeId();
+            int baseModeId = defaultMode.getModeId();
             if (availableModes.length > 0) {
-                defaultModeId = availableModes[0];
+                baseModeId = availableModes[0];
             }
             // filterModes function is going to filter the modes based on the voting system. If
             // the application requests a given mode with preferredModeId function, it will be
-            // stored as defaultModeId.
+            // stored as baseModeId.
             return new DesiredDisplayModeSpecs(
-                    defaultModeId, new RefreshRateRange(minRefreshRate, maxRefreshRate));
+                    baseModeId, new RefreshRateRange(minRefreshRate, maxRefreshRate));
         }
     }
 
@@ -526,7 +526,7 @@
     }
 
     /**
-     * Information about the desired display mode to be set by the system. Includes the default
+     * Information about the desired display mode to be set by the system. Includes the base
      * mode ID and refresh rate range.
      *
      * We have this class in addition to SurfaceControl.DesiredDisplayConfigSpecs to make clear the
@@ -535,10 +535,10 @@
      */
     public static final class DesiredDisplayModeSpecs {
         /**
-         * Default mode ID. This is what system defaults to for all other settings, or
+         * Base mode ID. This is what system defaults to for all other settings, or
          * if the refresh rate range is not available.
          */
-        public int defaultModeId;
+        public int baseModeId;
         /**
          * The refresh rate range.
          */
@@ -548,9 +548,8 @@
             refreshRateRange = new RefreshRateRange();
         }
 
-        public DesiredDisplayModeSpecs(
-                int defaultModeId, @NonNull RefreshRateRange refreshRateRange) {
-            this.defaultModeId = defaultModeId;
+        public DesiredDisplayModeSpecs(int baseModeId, @NonNull RefreshRateRange refreshRateRange) {
+            this.baseModeId = baseModeId;
             this.refreshRateRange = refreshRateRange;
         }
 
@@ -559,7 +558,7 @@
          */
         @Override
         public String toString() {
-            return String.format("defaultModeId=%d min=%.0f max=%.0f", defaultModeId,
+            return String.format("baseModeId=%d min=%.0f max=%.0f", baseModeId,
                     refreshRateRange.min, refreshRateRange.max);
         }
         /**
@@ -577,7 +576,7 @@
 
             DesiredDisplayModeSpecs desiredDisplayModeSpecs = (DesiredDisplayModeSpecs) other;
 
-            if (defaultModeId != desiredDisplayModeSpecs.defaultModeId) {
+            if (baseModeId != desiredDisplayModeSpecs.baseModeId) {
                 return false;
             }
             if (!refreshRateRange.equals(desiredDisplayModeSpecs.refreshRateRange)) {
@@ -588,14 +587,14 @@
 
         @Override
         public int hashCode() {
-            return Objects.hash(defaultModeId, refreshRateRange);
+            return Objects.hash(baseModeId, refreshRateRange);
         }
 
         /**
          * Copy values from the other object.
          */
         public void copyFrom(DesiredDisplayModeSpecs other) {
-            defaultModeId = other.defaultModeId;
+            baseModeId = other.baseModeId;
             refreshRateRange.min = other.refreshRateRange.min;
             refreshRateRange.max = other.refreshRateRange.max;
         }
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index cf94d46..fb8a419 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -272,14 +272,14 @@
 
             // Check whether surface flinger spontaneously changed display config specs out from
             // under us. If so, schedule a traversal to reapply our display config specs.
-            if (mDisplayModeSpecs.defaultModeId != 0) {
-                int activeDefaultMode =
+            if (mDisplayModeSpecs.baseModeId != 0) {
+                int activeBaseMode =
                         findMatchingModeIdLocked(physicalDisplayConfigSpecs.defaultConfig);
                 // If we can't map the defaultConfig index to a mode, then the physical display
                 // configs must have changed, and the code below for handling changes to the
                 // list of available modes will take care of updating display config specs.
-                if (activeDefaultMode != 0) {
-                    if (mDisplayModeSpecs.defaultModeId != activeDefaultMode
+                if (activeBaseMode != 0) {
+                    if (mDisplayModeSpecs.baseModeId != activeBaseMode
                             || mDisplayModeSpecs.refreshRateRange.min
                                     != physicalDisplayConfigSpecs.minRefreshRate
                             || mDisplayModeSpecs.refreshRateRange.max
@@ -312,14 +312,14 @@
                 mDefaultModeId = activeRecord.mMode.getModeId();
             }
 
-            // Determine whether the display mode specs' default mode is still there.
-            if (mSupportedModes.indexOfKey(mDisplayModeSpecs.defaultModeId) < 0) {
-                if (mDisplayModeSpecs.defaultModeId != 0) {
+            // Determine whether the display mode specs' base mode is still there.
+            if (mSupportedModes.indexOfKey(mDisplayModeSpecs.baseModeId) < 0) {
+                if (mDisplayModeSpecs.baseModeId != 0) {
                     Slog.w(TAG,
-                            "DisplayModeSpecs default mode no longer available, using currently"
-                                    + " active mode as default.");
+                            "DisplayModeSpecs base mode no longer available, using currently"
+                                    + " active mode.");
                 }
-                mDisplayModeSpecs.defaultModeId = activeRecord.mMode.getModeId();
+                mDisplayModeSpecs.baseModeId = activeRecord.mMode.getModeId();
                 mDisplayModeSpecsInvalid = true;
             }
 
@@ -648,13 +648,13 @@
         @Override
         public void setDesiredDisplayModeSpecsLocked(
                 DisplayModeDirector.DesiredDisplayModeSpecs displayModeSpecs) {
-            if (displayModeSpecs.defaultModeId == 0) {
+            if (displayModeSpecs.baseModeId == 0) {
                 // Bail if the caller is requesting a null mode. We'll get called again shortly with
                 // a valid mode.
                 return;
             }
-            int defaultPhysIndex = findDisplayInfoIndexLocked(displayModeSpecs.defaultModeId);
-            if (defaultPhysIndex < 0) {
+            int basePhysIndex = findDisplayInfoIndexLocked(displayModeSpecs.baseModeId);
+            if (basePhysIndex < 0) {
                 // When a display is hotplugged, it's possible for a mode to be removed that was
                 // previously valid. Because of the way display changes are propagated through the
                 // framework, and the caching of the display mode specs in LogicalDisplay, it's
@@ -662,8 +662,7 @@
                 // mode. This should only happen in extremely rare cases. A followup call will
                 // contain a valid mode id.
                 Slog.w(TAG,
-                        "Ignoring request for invalid default mode id "
-                                + displayModeSpecs.defaultModeId);
+                        "Ignoring request for invalid base mode id " + displayModeSpecs.baseModeId);
                 updateDeviceInfoLocked();
                 return;
             }
@@ -673,7 +672,7 @@
                 getHandler().sendMessage(PooledLambda.obtainMessage(
                         LocalDisplayDevice::setDesiredDisplayModeSpecsAsync, this,
                         getDisplayTokenLocked(),
-                        new SurfaceControl.DesiredDisplayConfigSpecs(defaultPhysIndex,
+                        new SurfaceControl.DesiredDisplayConfigSpecs(basePhysIndex,
                                 mDisplayModeSpecs.refreshRateRange.min,
                                 mDisplayModeSpecs.refreshRateRange.max)));
             }
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index b6255d1..c8e5f6c 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -317,7 +317,7 @@
         @Override
         public void setDesiredDisplayModeSpecsLocked(
                 DisplayModeDirector.DesiredDisplayModeSpecs displayModeSpecs) {
-            final int id = displayModeSpecs.defaultModeId;
+            final int id = displayModeSpecs.baseModeId;
             int index = -1;
             if (id == 0) {
                 // Use the default.
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index c58000f..8206fef 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -41,7 +41,6 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.infra.AbstractRemoteService;
 import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
@@ -713,8 +712,8 @@
     }
 
     /**
-     * Gets a list of all supported users (i.e., those that pass the {@link #isSupported(UserInfo)}
-     * check).
+     * Gets a list of all supported users (i.e., those that pass the
+     * {@link #isSupportedUser(TargetUser)}check).
      */
     @NonNull
     protected List<UserInfo> getSupportedUsers() {
@@ -723,7 +722,7 @@
         final List<UserInfo> supportedUsers = new ArrayList<>(size);
         for (int i = 0; i < size; i++) {
             final UserInfo userInfo = allUsers[i];
-            if (isSupported(userInfo)) {
+            if (isSupportedUser(new TargetUser(userInfo))) {
                 supportedUsers.add(userInfo);
             }
         }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index ae6d63a..90358ca 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -83,11 +83,11 @@
 import android.widget.Toast;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
-import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
@@ -186,6 +186,9 @@
     // The associations of input devices to displays by port. Maps from input device port (String)
     // to display id (int). Currently only accessed by InputReader.
     private final Map<String, Integer> mStaticAssociations;
+    private final Object mAssociationsLock = new Object();
+    @GuardedBy("mAssociationLock")
+    private final Map<String, Integer> mRuntimeAssociations = new HashMap<String, Integer>();
 
     private static native long nativeInit(InputManagerService service,
             Context context, MessageQueue messageQueue);
@@ -240,6 +243,7 @@
     private static native void nativeSetCustomPointerIcon(long ptr, PointerIcon icon);
     private static native void nativeSetPointerCapture(long ptr, boolean detached);
     private static native boolean nativeCanDispatchToDisplay(long ptr, int deviceId, int displayId);
+    private static native void nativeNotifyPortAssociationsChanged(long ptr);
 
     // Input event injection constants defined in InputDispatcher.h.
     private static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0;
@@ -1723,6 +1727,49 @@
         nativeSetCustomPointerIcon(mPtr, icon);
     }
 
+    /**
+     * Add a runtime association between the input port and the display port. This overrides any
+     * static associations.
+     * @param inputPort The port of the input device.
+     * @param displayPort The physical port of the associated display.
+     */
+    @Override // Binder call
+    public void addPortAssociation(@NonNull String inputPort, int displayPort) {
+        if (!checkCallingPermission(
+                android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT,
+                "addPortAssociation()")) {
+            throw new SecurityException(
+                    "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT permission");
+        }
+
+        Objects.requireNonNull(inputPort);
+        synchronized (mAssociationsLock) {
+            mRuntimeAssociations.put(inputPort, displayPort);
+        }
+        nativeNotifyPortAssociationsChanged(mPtr);
+    }
+
+    /**
+     * Remove the runtime association between the input port and the display port. Any existing
+     * static association for the cleared input port will be restored.
+     * @param inputPort The port of the input device to be cleared.
+     */
+    @Override // Binder call
+    public void removePortAssociation(@NonNull String inputPort) {
+        if (!checkCallingPermission(
+                android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT,
+                "clearPortAssociations()")) {
+            throw new SecurityException(
+                    "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT permission");
+        }
+
+        Objects.requireNonNull(inputPort);
+        synchronized (mAssociationsLock) {
+            mRuntimeAssociations.remove(inputPort);
+        }
+        nativeNotifyPortAssociationsChanged(mPtr);
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -1743,6 +1790,16 @@
                 pw.println("  display: " + v);
             });
         }
+
+        synchronized (mAssociationsLock) {
+            if (!mRuntimeAssociations.isEmpty()) {
+                pw.println("Runtime Associations:");
+                mRuntimeAssociations.forEach((k, v) -> {
+                    pw.print("  port: " + k);
+                    pw.println("  display: " + v);
+                });
+            }
+        }
     }
 
     private boolean checkCallingPermission(String permission, String func) {
@@ -1766,6 +1823,7 @@
     @Override
     public void monitor() {
         synchronized (mInputFilterLock) { }
+        synchronized (mAssociationsLock) { /* Test if blocked by associations lock. */}
         nativeMonitor(mPtr);
     }
 
@@ -1930,7 +1988,7 @@
      * @return Flattened list
      */
     private static List<String> flatten(@NonNull Map<String, Integer> map) {
-        List<String> list = new ArrayList<>(map.size() * 2);
+        final List<String> list = new ArrayList<>(map.size() * 2);
         map.forEach((k, v)-> {
             list.add(k);
             list.add(v.toString());
@@ -1943,11 +2001,11 @@
      * directory.
      */
     private static Map<String, Integer> loadStaticInputPortAssociations() {
-        File baseDir = Environment.getVendorDirectory();
-        File confFile = new File(baseDir, PORT_ASSOCIATIONS_PATH);
+        final File baseDir = Environment.getVendorDirectory();
+        final File confFile = new File(baseDir, PORT_ASSOCIATIONS_PATH);
 
         try {
-            InputStream stream = new FileInputStream(confFile);
+            final InputStream stream = new FileInputStream(confFile);
             return ConfigurationProcessor.processInputPortAssociations(stream);
         } catch (FileNotFoundException e) {
             // Most of the time, file will not exist, which is expected.
@@ -1960,7 +2018,14 @@
 
     // Native callback
     private String[] getInputPortAssociations() {
-        List<String> associationList = flatten(mStaticAssociations);
+        final Map<String, Integer> associations = new HashMap<>(mStaticAssociations);
+
+        // merge the runtime associations.
+        synchronized (mAssociationsLock) {
+            associations.putAll(mRuntimeAssociations);
+        }
+
+        final List<String> associationList = flatten(associations);
         return associationList.toArray(new String[0]);
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index c40e8af..2c3c70f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -72,6 +72,18 @@
             AutofillId autofillId, IInlineSuggestionsRequestCallback cb);
 
     /**
+     * Force switch to the enabled input method by {@code imeId} for current user. If the input
+     * method with {@code imeId} is not enabled or not installed, do nothing.
+     *
+     * @param imeId  The input method ID to be switched to.
+     * @param userId The user ID to be queried.
+     * @return {@code true} if the current input method was successfully switched to the input
+     * method by {@code imeId}; {@code false} the input method with {@code imeId} is not available
+     * to be switched.
+     */
+    public abstract boolean switchToInputMethod(String imeId, @UserIdInt int userId);
+
+    /**
      * Fake implementation of {@link InputMethodManagerInternal}.  All the methods do nothing.
      */
     private static final InputMethodManagerInternal NOP =
@@ -98,6 +110,11 @@
                 public void onCreateInlineSuggestionsRequest(ComponentName componentName,
                         AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
                 }
+
+                @Override
+                public boolean switchToInputMethod(String imeId, int userId) {
+                    return false;
+                }
             };
 
     /**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8b6b614..d4b13fa 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4478,6 +4478,38 @@
         }
     }
 
+    private boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
+        synchronized (mMethodMap) {
+            if (userId == mSettings.getCurrentUserId()) {
+                if (!mMethodMap.containsKey(imeId)
+                        || !mSettings.getEnabledInputMethodListLocked()
+                                .contains(mMethodMap.get(imeId))) {
+                    return false; // IME is not is found or not enabled.
+                }
+                setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
+                return true;
+            }
+            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+            final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
+            final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
+                    new ArrayMap<>();
+            AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+            queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
+                    methodMap, methodList);
+            final InputMethodSettings settings = new InputMethodSettings(
+                    mContext.getResources(), mContext.getContentResolver(), methodMap,
+                    userId, false);
+            if (!methodMap.containsKey(imeId)
+                    || !settings.getEnabledInputMethodListLocked()
+                            .contains(methodMap.get(imeId))) {
+                return false; // IME is not is found or not enabled.
+            }
+            settings.putSelectedInputMethod(imeId);
+            settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+            return true;
+        }
+    }
+
     private static final class LocalServiceImpl extends InputMethodManagerInternal {
         @NonNull
         private final InputMethodManagerService mService;
@@ -4514,6 +4546,11 @@
                 AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
             mService.onCreateInlineSuggestionsRequest(componentName, autofillId, cb);
         }
+
+        @Override
+        public boolean switchToInputMethod(String imeId, int userId) {
+            return mService.switchToInputMethod(imeId, userId);
+        }
     }
 
     @BinderThread
@@ -5065,31 +5102,7 @@
                 if (!userHasDebugPriv(userId, shellCommand)) {
                     continue;
                 }
-                boolean failedToSelectUnknownIme = false;
-                if (userId == mSettings.getCurrentUserId()) {
-                    if (mMethodMap.containsKey(imeId)) {
-                        setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
-                    } else {
-                        failedToSelectUnknownIme = true;
-                    }
-                } else {
-                    final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-                    final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
-                    final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
-                            new ArrayMap<>();
-                    AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
-                    queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                            methodMap, methodList);
-                    final InputMethodSettings settings = new InputMethodSettings(
-                            mContext.getResources(), mContext.getContentResolver(), methodMap,
-                            userId, false);
-                    if (methodMap.containsKey(imeId)) {
-                        settings.putSelectedInputMethod(imeId);
-                        settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
-                    } else {
-                        failedToSelectUnknownIme = true;
-                    }
-                }
+                boolean failedToSelectUnknownIme = !switchToInputMethod(imeId, userId);
                 if (failedToSelectUnknownIme) {
                     error.print("Unknown input method ");
                     error.print(imeId);
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 1f9379c..20d7955 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -200,6 +200,12 @@
                                 Slog.w(TAG, "Failed to call onInlineSuggestionsUnsupported.", e);
                             }
                         }
+
+                        @Override
+                        public boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
+                            reportNotSupported();
+                            return false;
+                        }
                     });
         }
 
diff --git a/services/core/java/com/android/server/integrity/IntegrityFileManager.java b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
index f90fab4..17a4b9c 100644
--- a/services/core/java/com/android/server/integrity/IntegrityFileManager.java
+++ b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
@@ -25,6 +25,7 @@
 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.RuleIndexRange;
 import com.android.server.integrity.parser.RuleIndexingController;
 import com.android.server.integrity.parser.RuleMetadataParser;
 import com.android.server.integrity.parser.RuleParseException;
@@ -38,6 +39,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 
@@ -152,14 +154,19 @@
             throws IOException, RuleParseException {
         synchronized (RULES_LOCK) {
             // Try to identify indexes from the index file.
-            List<List<Integer>> ruleReadingIndexes =
-                    mRuleIndexingController.identifyRulesToEvaluate(appInstallMetadata);
+            List<RuleIndexRange> ruleReadingIndexes;
+            try {
+                ruleReadingIndexes =
+                        mRuleIndexingController.identifyRulesToEvaluate(appInstallMetadata);
+            } catch (Exception e) {
+                Slog.w(TAG, "Error identifying the rule indexes. Trying unindexed.", e);
+                ruleReadingIndexes = Collections.emptyList();
+            }
 
-            // Read the rules based on the index information.
-            // TODO(b/145493956): Provide the identified indexes to the rule reader.
+            // Read the rules based on the index information when available.
             try (FileInputStream inputStream =
                          new FileInputStream(new File(mRulesDir, RULES_FILE))) {
-                List<Rule> rules = mRuleParser.parse(inputStream);
+                List<Rule> rules = mRuleParser.parse(inputStream, ruleReadingIndexes);
                 return rules;
             }
         }
diff --git a/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java b/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java
new file mode 100644
index 0000000..e555e3e
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/model/BitTrackedInputStream.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.model;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An input stream that tracks the total number read bytes since construction and allows moving
+ * fast forward to a certain byte any time during the execution.
+ *
+ * This class is used for efficient reading of rules based on the rule indexing.
+ */
+public class BitTrackedInputStream extends BitInputStream {
+
+    private static int sReadBitsCount;
+
+    /** Constructor with byte array. */
+    public BitTrackedInputStream(byte[] inputStream) {
+        super(inputStream);
+        sReadBitsCount = 0;
+    }
+
+    /** Constructor with input stream. */
+    public BitTrackedInputStream(InputStream inputStream) {
+        super(inputStream);
+        sReadBitsCount = 0;
+    }
+
+    /** Obtains an integer value of the next {@code numOfBits}. */
+    @Override
+    public int getNext(int numOfBits) throws IOException {
+        sReadBitsCount += numOfBits;
+        return super.getNext(numOfBits);
+    }
+
+    /** Returns the current cursor position showing the number of bits that are read. */
+    public int getReadBitsCount() {
+        return sReadBitsCount;
+    }
+
+    /**
+     * Sets the cursor to the specified byte location.
+     *
+     * Note that the integer parameter specifies the location in bytes -- not bits.
+     */
+    public void setCursorToByteLocation(int byteLocation) throws IOException {
+        int bitCountToRead = byteLocation * 8 - sReadBitsCount;
+        if (bitCountToRead < 0) {
+            throw new IllegalStateException("The byte position is already read.");
+        }
+        super.getNext(bitCountToRead);
+        sReadBitsCount = byteLocation * 8;
+    }
+}
diff --git a/services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
similarity index 96%
rename from services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java
rename to services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
index 62815a9..f575599 100644
--- a/services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java
+++ b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.integrity.serializer;
+package com.android.server.integrity.model;
 
 import java.io.IOException;
 import java.io.OutputStream;
diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
index cbb6e4e..e744326 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
@@ -37,71 +37,103 @@
 import android.content.integrity.Formula;
 import android.content.integrity.Rule;
 
-import com.android.server.integrity.model.BitInputStream;
+import com.android.server.integrity.model.BitTrackedInputStream;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /** A helper class to parse rules into the {@link Rule} model from Binary representation. */
 public class RuleBinaryParser implements RuleParser {
 
-    private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
-
     @Override
     public List<Rule> parse(byte[] ruleBytes) throws RuleParseException {
         try {
-            BitInputStream bitInputStream = new BitInputStream(ruleBytes);
-            return parseRules(bitInputStream);
+            BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(ruleBytes);
+            return parseRules(bitTrackedInputStream, /* indexRanges= */ Collections.emptyList());
         } catch (Exception e) {
             throw new RuleParseException(e.getMessage(), e);
         }
     }
 
     @Override
-    public List<Rule> parse(InputStream inputStream) throws RuleParseException {
+    public List<Rule> parse(InputStream inputStream, List<RuleIndexRange> indexRanges)
+            throws RuleParseException {
         try {
-            BitInputStream bitInputStream = new BitInputStream(inputStream);
-            return parseRules(bitInputStream);
+            BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(inputStream);
+            return parseRules(bitTrackedInputStream, indexRanges);
         } catch (Exception e) {
             throw new RuleParseException(e.getMessage(), e);
         }
     }
 
-    private List<Rule> parseRules(BitInputStream bitInputStream) throws IOException {
-        List<Rule> parsedRules = new ArrayList<>();
+    private List<Rule> parseRules(
+            BitTrackedInputStream bitTrackedInputStream,
+            List<RuleIndexRange> indexRanges)
+            throws IOException {
 
         // Read the rule binary file format version.
-        bitInputStream.getNext(FORMAT_VERSION_BITS);
+        bitTrackedInputStream.getNext(FORMAT_VERSION_BITS);
 
-        while (bitInputStream.hasNext()) {
-            if (bitInputStream.getNext(SIGNAL_BIT) == 1) {
-                parsedRules.add(parseRule(bitInputStream));
+        return indexRanges.isEmpty()
+                ? parseAllRules(bitTrackedInputStream)
+                : parseIndexedRules(bitTrackedInputStream, indexRanges);
+    }
+
+    private List<Rule> parseAllRules(BitTrackedInputStream bitTrackedInputStream)
+            throws IOException {
+        List<Rule> parsedRules = new ArrayList<>();
+
+        while (bitTrackedInputStream.hasNext()) {
+            if (bitTrackedInputStream.getNext(SIGNAL_BIT) == 1) {
+                parsedRules.add(parseRule(bitTrackedInputStream));
             }
         }
 
         return parsedRules;
     }
 
-    private Rule parseRule(BitInputStream bitInputStream) throws IOException {
-        Formula formula = parseFormula(bitInputStream);
-        int effect = bitInputStream.getNext(EFFECT_BITS);
+    private List<Rule> parseIndexedRules(
+            BitTrackedInputStream bitTrackedInputStream, List<RuleIndexRange> indexRanges)
+            throws IOException {
+        List<Rule> parsedRules = new ArrayList<>();
 
-        if (bitInputStream.getNext(SIGNAL_BIT) != 1) {
+        for (RuleIndexRange range : indexRanges) {
+            // Skip the rules that are not in the range.
+            bitTrackedInputStream.setCursorToByteLocation(range.getStartIndex());
+
+            // Read the rules until we reach the end index.
+            while (bitTrackedInputStream.hasNext()
+                    && bitTrackedInputStream.getReadBitsCount() < range.getEndIndex()) {
+                if (bitTrackedInputStream.getNext(SIGNAL_BIT) == 1) {
+                    parsedRules.add(parseRule(bitTrackedInputStream));
+                }
+            }
+        }
+
+        return parsedRules;
+    }
+
+    private Rule parseRule(BitTrackedInputStream bitTrackedInputStream) throws IOException {
+        Formula formula = parseFormula(bitTrackedInputStream);
+        int effect = bitTrackedInputStream.getNext(EFFECT_BITS);
+
+        if (bitTrackedInputStream.getNext(SIGNAL_BIT) != 1) {
             throw new IllegalArgumentException("A rule must end with a '1' bit.");
         }
 
         return new Rule(formula, effect);
     }
 
-    private Formula parseFormula(BitInputStream bitInputStream) throws IOException {
-        int separator = bitInputStream.getNext(SEPARATOR_BITS);
+    private Formula parseFormula(BitTrackedInputStream bitTrackedInputStream) throws IOException {
+        int separator = bitTrackedInputStream.getNext(SEPARATOR_BITS);
         switch (separator) {
             case ATOMIC_FORMULA_START:
-                return parseAtomicFormula(bitInputStream);
+                return parseAtomicFormula(bitTrackedInputStream);
             case COMPOUND_FORMULA_START:
-                return parseCompoundFormula(bitInputStream);
+                return parseCompoundFormula(bitTrackedInputStream);
             case COMPOUND_FORMULA_END:
                 return null;
             default:
@@ -110,37 +142,40 @@
         }
     }
 
-    private CompoundFormula parseCompoundFormula(BitInputStream bitInputStream) throws IOException {
-        int connector = bitInputStream.getNext(CONNECTOR_BITS);
+    private CompoundFormula parseCompoundFormula(BitTrackedInputStream bitTrackedInputStream)
+            throws IOException {
+        int connector = bitTrackedInputStream.getNext(CONNECTOR_BITS);
         List<Formula> formulas = new ArrayList<>();
 
-        Formula parsedFormula = parseFormula(bitInputStream);
+        Formula parsedFormula = parseFormula(bitTrackedInputStream);
         while (parsedFormula != null) {
             formulas.add(parsedFormula);
-            parsedFormula = parseFormula(bitInputStream);
+            parsedFormula = parseFormula(bitTrackedInputStream);
         }
 
         return new CompoundFormula(connector, formulas);
     }
 
-    private AtomicFormula parseAtomicFormula(BitInputStream bitInputStream) throws IOException {
-        int key = bitInputStream.getNext(KEY_BITS);
-        int operator = bitInputStream.getNext(OPERATOR_BITS);
+    private AtomicFormula parseAtomicFormula(BitTrackedInputStream bitTrackedInputStream)
+            throws IOException {
+        int key = bitTrackedInputStream.getNext(KEY_BITS);
+        int operator = bitTrackedInputStream.getNext(OPERATOR_BITS);
 
         switch (key) {
             case AtomicFormula.PACKAGE_NAME:
             case AtomicFormula.APP_CERTIFICATE:
             case AtomicFormula.INSTALLER_NAME:
             case AtomicFormula.INSTALLER_CERTIFICATE:
-                boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1;
-                int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS);
-                String stringValue = getStringValue(bitInputStream, valueSize, isHashedValue);
+                boolean isHashedValue = bitTrackedInputStream.getNext(IS_HASHED_BITS) == 1;
+                int valueSize = bitTrackedInputStream.getNext(VALUE_SIZE_BITS);
+                String stringValue = getStringValue(bitTrackedInputStream, valueSize,
+                        isHashedValue);
                 return new AtomicFormula.StringAtomicFormula(key, stringValue, isHashedValue);
             case AtomicFormula.VERSION_CODE:
-                int intValue = getIntValue(bitInputStream);
+                int intValue = getIntValue(bitTrackedInputStream);
                 return new AtomicFormula.IntAtomicFormula(key, operator, intValue);
             case AtomicFormula.PRE_INSTALLED:
-                boolean booleanValue = getBooleanValue(bitInputStream);
+                boolean booleanValue = getBooleanValue(bitTrackedInputStream);
                 return new AtomicFormula.BooleanAtomicFormula(key, booleanValue);
             default:
                 throw new IllegalArgumentException(String.format("Unknown key: %d", key));
diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
new file mode 100644
index 0000000..8c8450e
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.parser;
+
+import android.annotation.Nullable;
+
+/**
+ * A wrapper class to represent an indexing range that is identified by the {@link
+ * RuleIndexingController}.
+ */
+public class RuleIndexRange {
+    private static int sStartIndex;
+    private static int sEndIndex;
+
+    /** Constructor with start and end indexes. */
+    public RuleIndexRange(int startIndex, int endIndex) {
+        this.sStartIndex = startIndex;
+        this.sEndIndex = endIndex;
+    }
+
+    /** Returns the startIndex. */
+    public int getStartIndex() {
+        return sStartIndex;
+    }
+
+    /** Returns the end index. */
+    public int getEndIndex() {
+        return sEndIndex;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object object) {
+        return sStartIndex == ((RuleIndexRange) object).getStartIndex()
+                && sEndIndex == ((RuleIndexRange) object).getEndIndex();
+    }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
index b642fa6..c971322 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
@@ -17,6 +17,7 @@
 package com.android.server.integrity.parser;
 
 import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
+import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
 import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue;
 import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue;
 
@@ -24,25 +25,27 @@
 
 import com.android.server.integrity.model.BitInputStream;
 
-import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
 
 /** Helper class to identify the necessary indexes that needs to be read. */
 public class RuleIndexingController {
 
-    private static TreeMap<String, Integer> sPackageNameBasedIndexes;
-    private static TreeMap<String, Integer> sAppCertificateBasedIndexes;
-    private static TreeMap<String, Integer> sUnindexedRuleIndexes;
+    private static LinkedHashMap<String, Integer> sPackageNameBasedIndexes;
+    private static LinkedHashMap<String, Integer> sAppCertificateBasedIndexes;
+    private static LinkedHashMap<String, Integer> sUnindexedRuleIndexes;
 
     /**
      * Provide the indexing file to read and the object will be constructed by reading and
      * identifying the indexes.
      */
-    public RuleIndexingController(FileInputStream fileInputStream) throws IOException {
-        BitInputStream bitInputStream = new BitInputStream(fileInputStream);
+    public RuleIndexingController(InputStream inputStream) throws IOException {
+        BitInputStream bitInputStream = new BitInputStream(inputStream);
         sPackageNameBasedIndexes = getNextIndexGroup(bitInputStream);
         sAppCertificateBasedIndexes = getNextIndexGroup(bitInputStream);
         sUnindexedRuleIndexes = getNextIndexGroup(bitInputStream);
@@ -52,24 +55,57 @@
      * Returns a list of integers with the starting and ending bytes of the rules that needs to be
      * read and evaluated.
      */
-    public List<List<Integer>> identifyRulesToEvaluate(AppInstallMetadata appInstallMetadata) {
-        // TODO(b/145493956): Identify and return the indexes that needs to be read.
-        return new ArrayList<>();
+    public List<RuleIndexRange> identifyRulesToEvaluate(AppInstallMetadata appInstallMetadata) {
+        ArrayList<RuleIndexRange> indexRanges = new ArrayList();
+
+        // Add the range for package name indexes rules.
+        indexRanges.add(
+                searchIndexingKeysRangeContainingKey(
+                        sPackageNameBasedIndexes, appInstallMetadata.getPackageName()));
+
+        // Add the range for app certificate indexes rules.
+        indexRanges.add(
+                searchIndexingKeysRangeContainingKey(
+                        sAppCertificateBasedIndexes, appInstallMetadata.getAppCertificate()));
+
+        // Add the range for unindexed rules.
+        indexRanges.add(
+                new RuleIndexRange(
+                        sUnindexedRuleIndexes.get(START_INDEXING_KEY),
+                        sUnindexedRuleIndexes.get(END_INDEXING_KEY)));
+
+        return indexRanges;
     }
 
-    private TreeMap<String, Integer> getNextIndexGroup(BitInputStream bitInputStream)
+    private LinkedHashMap<String, Integer> getNextIndexGroup(BitInputStream bitInputStream)
             throws IOException {
-        TreeMap<String, Integer> keyToIndexMap = new TreeMap<>();
+        LinkedHashMap<String, Integer> keyToIndexMap = new LinkedHashMap<>();
         while (bitInputStream.hasNext()) {
             String key = getStringValue(bitInputStream);
             int value = getIntValue(bitInputStream);
 
             keyToIndexMap.put(key, value);
 
-            if (key == END_INDEXING_KEY) {
+            if (key.matches(END_INDEXING_KEY)) {
                 break;
             }
         }
         return keyToIndexMap;
     }
+
+    private RuleIndexRange searchIndexingKeysRangeContainingKey(
+            LinkedHashMap<String, Integer> indexMap, String searchedKey) {
+        TreeSet<String> keyTreeSet =
+                indexMap.keySet().stream()
+                        .filter(key -> !key.matches(START_INDEXING_KEY) && !key.matches(
+                                END_INDEXING_KEY))
+                        .collect(Collectors.toCollection(TreeSet::new));
+
+        String minIndex = keyTreeSet.floor(searchedKey);
+        String maxIndex = keyTreeSet.ceiling(searchedKey);
+
+        return new RuleIndexRange(
+                indexMap.get(minIndex == null ? START_INDEXING_KEY : minIndex),
+                indexMap.get(maxIndex == null ? END_INDEXING_KEY : maxIndex));
+    }
 }
diff --git a/services/core/java/com/android/server/integrity/parser/RuleParser.java b/services/core/java/com/android/server/integrity/parser/RuleParser.java
index 81783d5..a8e9f61 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleParser.java
@@ -28,5 +28,6 @@
     List<Rule> parse(byte[] ruleBytes) throws RuleParseException;
 
     /** Parse rules from an input stream. */
-    List<Rule> parse(InputStream inputStream) throws RuleParseException;
+    List<Rule> parse(InputStream inputStream, List<RuleIndexRange> ruleIndexRanges)
+            throws RuleParseException;
 }
diff --git a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
index d405583..497be84 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
@@ -62,7 +62,8 @@
     }
 
     @Override
-    public List<Rule> parse(InputStream inputStream) throws RuleParseException {
+    public List<Rule> parse(InputStream inputStream, List<RuleIndexRange> indexRanges)
+            throws RuleParseException {
         try {
             XmlPullParser xmlPullParser = Xml.newPullParser();
             xmlPullParser.setInput(inputStream, StandardCharsets.UTF_8.name());
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
index b8791c3..d3588d3 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
@@ -42,11 +42,13 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.integrity.IntegrityUtils;
 import com.android.server.integrity.model.BitOutputStream;
+import com.android.server.integrity.model.ByteTrackedOutputStream;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -81,35 +83,36 @@
             Map<Integer, TreeMap<String, List<Rule>>> indexedRules =
                     RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
 
+            // Serialize the rules.
             ByteTrackedOutputStream ruleFileByteTrackedOutputStream =
                     new ByteTrackedOutputStream(rulesFileOutputStream);
-
             serializeRuleFileMetadata(formatVersion, ruleFileByteTrackedOutputStream);
-
             Map<String, Integer> packageNameIndexes =
                     serializeRuleList(indexedRules.get(PACKAGE_NAME_INDEXED),
                             ruleFileByteTrackedOutputStream);
-            indexingFileOutputStream.write(
-                    serializeIndexes(packageNameIndexes, /* isIndexed= */true));
-
             Map<String, Integer> appCertificateIndexes =
                     serializeRuleList(indexedRules.get(APP_CERTIFICATE_INDEXED),
                             ruleFileByteTrackedOutputStream);
-            indexingFileOutputStream.write(
-                    serializeIndexes(appCertificateIndexes, /* isIndexed= */true));
-
             Map<String, Integer> unindexedRulesIndexes =
                     serializeRuleList(indexedRules.get(NOT_INDEXED),
                             ruleFileByteTrackedOutputStream);
-            indexingFileOutputStream.write(
-                    serializeIndexes(unindexedRulesIndexes, /* isIndexed= */false));
+
+            // Serialize their indexes.
+            BitOutputStream indexingBitOutputStream = new BitOutputStream();
+            serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */true);
+            serializeIndexGroup(appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */
+                    true);
+            serializeIndexGroup(unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */
+                    false);
+            indexingFileOutputStream.write(indexingBitOutputStream.toByteArray());
         } catch (Exception e) {
             throw new RuleSerializeException(e.getMessage(), e);
         }
     }
 
     private void serializeRuleFileMetadata(Optional<Integer> formatVersion,
-            ByteTrackedOutputStream outputStream) throws IOException {
+            ByteTrackedOutputStream outputStream)
+            throws IOException {
         int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
 
         BitOutputStream bitOutputStream = new BitOutputStream();
@@ -124,7 +127,7 @@
                 "serializeRuleList should never be called with null rule list.");
 
         BitOutputStream bitOutputStream = new BitOutputStream();
-        Map<String, Integer> indexMapping = new TreeMap();
+        Map<String, Integer> indexMapping = new LinkedHashMap();
         int indexTracker = 0;
 
         indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount());
@@ -218,8 +221,8 @@
         }
     }
 
-    private byte[] serializeIndexes(Map<String, Integer> indexes, boolean isIndexed) {
-        BitOutputStream bitOutputStream = new BitOutputStream();
+    private void serializeIndexGroup(
+            Map<String, Integer> indexes, BitOutputStream bitOutputStream, boolean isIndexed) {
 
         // Output the starting location of this indexing group.
         serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */false,
@@ -242,7 +245,8 @@
         serializeStringValue(END_INDEXING_KEY, /*isHashedValue= */ false, bitOutputStream);
         serializeIntValue(indexes.get(END_INDEXING_KEY), bitOutputStream);
 
-        return bitOutputStream.toByteArray();
+        // This dummy bit is set for fixing the padding issue. songpan@ will fix it and remove it.
+        bitOutputStream.setNext();
     }
 
     private void serializeStringValue(
diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java
index ccfc98e..ed6a759 100644
--- a/services/core/java/com/android/server/location/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java
@@ -16,11 +16,11 @@
 
 package com.android.server.location;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.location.Location;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.WorkSource;
 
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
@@ -29,127 +29,336 @@
 import java.io.PrintWriter;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.UnaryOperator;
 
 /**
- * Location Manager's interface for location providers. Always starts as disabled.
+ * Base class for all location providers.
  *
  * @hide
  */
 public abstract class AbstractLocationProvider {
 
     /**
-     * Interface for communicating from a location provider back to the location service.
+     * Interface for listening to location providers.
      */
-    public interface LocationProviderManager {
+    public interface Listener {
 
         /**
-         * May be called to inform the location service of a change in this location provider's
-         * enabled/disabled state.
+         * Called when a provider's state changes. May be invoked from any thread. Will be
+         * invoked with a cleared binder identity.
          */
-        void onSetEnabled(boolean enabled);
+        void onStateChanged(State oldState, State newState);
 
         /**
-         * May be called to inform the location service of a change in this location provider's
-         * properties.
-         */
-        void onSetProperties(ProviderProperties properties);
-
-        /**
-         * May be called to inform the location service that this provider has a new location
-         * available.
+         * Called when a provider has a new location available. May be invoked from any thread. Will
+         * be invoked with a cleared binder identity.
          */
         void onReportLocation(Location location);
 
         /**
-         * May be called to inform the location service that this provider has a new location
-         * available.
+         * Called when a provider has a new location available. May be invoked from any thread. Will
+         * be invoked with a cleared binder identity.
          */
         void onReportLocation(List<Location> locations);
     }
 
-    protected final Context mContext;
-    private final LocationProviderManager mLocationProviderManager;
+    /**
+     * Holds a representation of the public state of a provider.
+     */
+    public static final class State {
 
-    protected AbstractLocationProvider(
-            Context context, LocationProviderManager locationProviderManager) {
+        /**
+         * Default state value for a location provider that is disabled with no properties and an
+         * empty provider package list.
+         */
+        public static final State EMPTY_STATE = new State(false, null,
+                Collections.emptySet());
+
+        /**
+         * The provider's enabled state.
+         */
+        public final boolean enabled;
+
+        /**
+         * The provider's properties.
+         */
+        @Nullable public final ProviderProperties properties;
+
+        /**
+         * The provider's package name list - provider packages may be afforded special privileges.
+         */
+        public final Set<String> providerPackageNames;
+
+        private State(boolean enabled, ProviderProperties properties,
+                Set<String> providerPackageNames) {
+            this.enabled = enabled;
+            this.properties = properties;
+            this.providerPackageNames = Objects.requireNonNull(providerPackageNames);
+        }
+
+        private State withEnabled(boolean enabled) {
+            if (enabled == this.enabled) {
+                return this;
+            } else {
+                return new State(enabled, properties, providerPackageNames);
+            }
+        }
+
+        private State withProperties(ProviderProperties properties) {
+            if (properties.equals(this.properties)) {
+                return this;
+            } else {
+                return new State(enabled, properties, providerPackageNames);
+            }
+        }
+
+        private State withProviderPackageNames(Set<String> providerPackageNames) {
+            if (providerPackageNames.equals(this.providerPackageNames)) {
+                return this;
+            } else {
+                return new State(enabled, properties, providerPackageNames);
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof State)) {
+                return false;
+            }
+            State state = (State) o;
+            return enabled == state.enabled && properties == state.properties
+                    && providerPackageNames.equals(state.providerPackageNames);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(enabled, properties, providerPackageNames);
+        }
+    }
+
+    // combines listener and state information so that they can be updated atomically with respect
+    // to each other and an ordering established.
+    private static class InternalState {
+        @Nullable public final Listener listener;
+        public final State state;
+
+        private InternalState(@Nullable Listener listener, State state) {
+            this.listener = listener;
+            this.state = state;
+        }
+
+        private InternalState withListener(Listener listener) {
+            if (listener == this.listener) {
+                return this;
+            } else {
+                return new InternalState(listener, state);
+            }
+        }
+
+        private InternalState withState(State state) {
+            if (state.equals(this.state)) {
+                return this;
+            } else {
+                return new InternalState(listener, state);
+            }
+        }
+
+        private InternalState withState(UnaryOperator<State> operator) {
+            return withState(operator.apply(state));
+        }
+    }
+
+    protected final Context mContext;
+    protected final Executor mExecutor;
+
+    // we use a lock-free implementation to update state to ensure atomicity between updating the
+    // provider state and setting the listener, so that the state updates a listener sees are
+    // consistent with when the listener was set (a listener should not see any updates that occur
+    // before it was set, and should not miss any updates that occur after it was set).
+    private final AtomicReference<InternalState> mInternalState;
+
+    protected AbstractLocationProvider(Context context, Executor executor) {
+        this(context, executor, Collections.singleton(context.getPackageName()));
+    }
+
+    protected AbstractLocationProvider(Context context, Executor executor,
+            Set<String> packageNames) {
         mContext = context;
-        mLocationProviderManager = locationProviderManager;
+        mExecutor = executor;
+        mInternalState = new AtomicReference<>(
+                new InternalState(null, State.EMPTY_STATE.withProviderPackageNames(packageNames)));
     }
 
     /**
-     * Call this method to report a change in provider enabled/disabled status. May be called from
-     * any thread.
+     * Sets the listener and returns the state at the moment the listener was set. The listener can
+     * expect to receive all state updates from after this point.
+     */
+    State setListener(@Nullable Listener listener) {
+        return mInternalState.updateAndGet(
+                internalState -> internalState.withListener(listener)).state;
+    }
+
+    /**
+     * Retrieves the state of the provider.
+     */
+    State getState() {
+        return mInternalState.get().state;
+    }
+
+    /**
+     * Sets the state of the provider to the new state.
+     */
+    void setState(State newState) {
+        InternalState oldInternalState = mInternalState.getAndUpdate(
+                internalState -> internalState.withState(newState));
+        if (newState.equals(oldInternalState.state)) {
+            return;
+        }
+
+        // we know that we only updated the state, so the listener for the old state is the same as
+        // the listener for the new state.
+        if (oldInternalState.listener != null) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                oldInternalState.listener.onStateChanged(oldInternalState.state, newState);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    private void setState(UnaryOperator<State> operator) {
+        InternalState oldInternalState = mInternalState.getAndUpdate(
+                internalState -> internalState.withState(operator));
+
+        // recreate the new state from our knowledge of the old state - unfortunately may result in
+        // an extra allocation, but oh well...
+        State newState = operator.apply(oldInternalState.state);
+
+        if (newState.equals(oldInternalState.state)) {
+            return;
+        }
+
+        // we know that we only updated the state, so the listener for the old state is the same as
+        // the listener for the new state.
+        if (oldInternalState.listener != null) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                oldInternalState.listener.onStateChanged(oldInternalState.state, newState);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    /**
+     * The current enabled state of this provider.
+     */
+    protected boolean isEnabled() {
+        return mInternalState.get().state.enabled;
+    }
+
+    /**
+     * The current provider properties of this provider.
+     */
+    @Nullable
+    protected ProviderProperties getProperties() {
+        return mInternalState.get().state.properties;
+    }
+
+    /**
+     * The current package set of this provider.
+     */
+    protected Set<String> getProviderPackages() {
+        return mInternalState.get().state.providerPackageNames;
+    }
+
+    /**
+     * Call this method to report a change in provider enabled/disabled status.
      */
     protected void setEnabled(boolean enabled) {
-        long identity = Binder.clearCallingIdentity();
-        try {
-            mLocationProviderManager.onSetEnabled(enabled);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        setState(state -> state.withEnabled(enabled));
     }
 
     /**
-     * Call this method to report a change in provider properties. May be called from
-     * any thread.
+     * Call this method to report a change in provider properties.
      */
     protected void setProperties(ProviderProperties properties) {
-        long identity = Binder.clearCallingIdentity();
-        try {
-            mLocationProviderManager.onSetProperties(properties);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        setState(state -> state.withProperties(properties));
     }
 
     /**
-     * Call this method to report a new location. May be called from any thread.
+     * Call this method to report a change in provider packages.
+     */
+    protected void setPackageNames(Set<String> packageNames) {
+        setState(state -> state.withProviderPackageNames(packageNames));
+    }
+
+    /**
+     * Call this method to report a new location.
      */
     protected void reportLocation(Location location) {
-        long identity = Binder.clearCallingIdentity();
-        try {
-            mLocationProviderManager.onReportLocation(location);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+        Listener listener = mInternalState.get().listener;
+        if (listener != null) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                listener.onReportLocation(location);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
     /**
-     * Call this method to report a new location. May be called from any thread.
+     * Call this method to report a new location.
      */
     protected void reportLocation(List<Location> locations) {
-        long identity = Binder.clearCallingIdentity();
-        try {
-            mLocationProviderManager.onReportLocation(locations);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+        Listener listener = mInternalState.get().listener;
+        if (listener != null) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                listener.onReportLocation(locations);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
     }
 
     /**
-     * Invoked by the location service to return a list of packages currently associated with this
-     * provider. May be called from any thread.
+     * Sets a new request and worksource for the provider.
      */
-    public List<String> getProviderPackages() {
-        return Collections.singletonList(mContext.getPackageName());
+    public final void setRequest(ProviderRequest request) {
+        // all calls into the provider must be moved onto the provider thread to prevent deadlock
+        mExecutor.execute(() -> onSetRequest(request));
     }
 
     /**
-     * Invoked by the location service to deliver a new request for fulfillment to the provider.
-     * Replaces any previous requests completely. Will always be invoked from the location service
-     * thread with a cleared binder identity.
+     * Always invoked on the provider executor.
      */
-    public abstract void onSetRequest(ProviderRequest request, WorkSource source);
+    protected abstract void onSetRequest(ProviderRequest request);
 
     /**
-     * Invoked by the location service to deliver a custom command to this provider. Will always be
-     * invoked from the location service thread with a cleared binder identity.
+     * Sends an extra command to the provider for it to interpret as it likes.
      */
-    public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) {}
+    public final void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
+        // all calls into the provider must be moved onto the provider thread to prevent deadlock
+        mExecutor.execute(() -> onExtraCommand(uid, pid, command, extras));
+    }
 
     /**
-     * Invoked by the location service to dump debug or log information. May be invoked from any
-     * thread.
+     * Always invoked on the provider executor.
+     */
+    protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {}
+
+    /**
+     * Dumps debug or log information. May be invoked from any thread.
      */
     public abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args);
 }
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index d8561b6..15cf190 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -43,6 +43,7 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
@@ -113,8 +114,15 @@
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
     private static final ProviderProperties PROPERTIES = new ProviderProperties(
-            true, true, false, false, true, true, true,
-            Criteria.POWER_HIGH, Criteria.ACCURACY_FINE);
+            /* requiresNetwork = */false,
+            /* requiresSatellite = */true,
+            /* requiresCell = */false,
+            /* hasMonetaryCost = */false,
+            /* supportAltitude = */true,
+            /* supportsSpeed = */true,
+            /* supportsBearing = */true,
+            Criteria.POWER_HIGH,
+            Criteria.ACCURACY_FINE);
 
     // these need to match GnssPositionMode enum in IGnss.hal
     private static final int GPS_POSITION_MODE_STANDALONE = 0;
@@ -616,13 +624,12 @@
         }
     }
 
-    public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager,
-            Looper looper) {
-        super(context, locationProviderManager);
+    public GnssLocationProvider(Context context, Handler handler) {
+        super(context, new HandlerExecutor(handler));
 
         ensureInitialized();
 
-        mLooper = looper;
+        mLooper = handler.getLooper();
 
         // Create a wake lock
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -639,7 +646,7 @@
         mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0);
 
         mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(context,
-                GnssLocationProvider.this::onNetworkAvailable, looper);
+                GnssLocationProvider.this::onNetworkAvailable, mLooper);
 
         // App ops service to keep track of who is accessing the GPS
         mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -649,7 +656,7 @@
                 BatteryStats.SERVICE_NAME));
 
         // Construct internal handler
-        mHandler = new ProviderHandler(looper);
+        mHandler = new ProviderHandler(mLooper);
 
         // Load GPS configuration and register listeners in the background:
         // some operations, such as opening files and registering broadcast receivers, can take a
@@ -693,10 +700,10 @@
         };
 
         mGnssMetrics = new GnssMetrics(mBatteryStats);
-        mNtpTimeHelper = new NtpTimeHelper(mContext, looper, this);
+        mNtpTimeHelper = new NtpTimeHelper(mContext, mLooper, this);
         GnssSatelliteBlacklistHelper gnssSatelliteBlacklistHelper =
                 new GnssSatelliteBlacklistHelper(mContext,
-                        looper, this);
+                        mLooper, this);
         mHandler.post(gnssSatelliteBlacklistHelper::updateSatelliteBlacklist);
         mGnssBatchingProvider = new GnssBatchingProvider();
         mGnssGeofenceProvider = new GnssGeofenceProvider();
@@ -1047,8 +1054,8 @@
     }
 
     @Override
-    public void onSetRequest(ProviderRequest request, WorkSource source) {
-        sendMessage(SET_REQUEST, 0, new GpsRequest(request, source));
+    public void onSetRequest(ProviderRequest request) {
+        sendMessage(SET_REQUEST, 0, new GpsRequest(request, request.workSource));
     }
 
     private void handleSetRequest(ProviderRequest request, WorkSource source) {
@@ -1185,7 +1192,7 @@
     }
 
     @Override
-    public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) {
+    public void onExtraCommand(int uid, int pid, String command, Bundle extras) {
 
         long identity = Binder.clearCallingIdentity();
         try {
@@ -2064,10 +2071,8 @@
         }
 
         /**
-         * This method is bound to {@link #GnssLocationProvider(Context, LocationProviderManager,
-         * Looper)}.
-         * It is in charge of loading properties and registering for events that will be posted to
-         * this handler.
+         * This method is bound to the constructor. It is in charge of loading properties and
+         * registering for events that will be posted to this handler.
          */
         private void handleInitialize() {
             // class_init_native() already initializes the GNSS service handle during class loading.
diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java
index 694f149..8a149af 100644
--- a/services/core/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/core/java/com/android/server/location/LocationProviderProxy.java
@@ -23,12 +23,13 @@
 import android.content.pm.PackageManager;
 import android.location.Location;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.WorkSource;
+import android.util.ArraySet;
 import android.util.Log;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.location.ILocationProvider;
 import com.android.internal.location.ILocationProviderManager;
 import com.android.internal.location.ProviderProperties;
@@ -39,10 +40,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Proxy for ILocationProvider implementations.
@@ -52,59 +51,64 @@
     private static final String TAG = "LocationProviderProxy";
     private static final boolean D = LocationManagerService.D;
 
-    // used to ensure that updates to mProviderPackages are atomic
-    private final Object mProviderPackagesLock = new Object();
-
-    // used to ensure that updates to mRequest and mWorkSource are atomic
-    private final Object mRequestLock = new Object();
+    private static final int MAX_ADDITIONAL_PACKAGES = 2;
 
     private final ILocationProviderManager.Stub mManager = new ILocationProviderManager.Stub() {
         // executed on binder thread
         @Override
         public void onSetAdditionalProviderPackages(List<String> packageNames) {
-            LocationProviderProxy.this.onSetAdditionalProviderPackages(packageNames);
+            int maxCount = Math.min(MAX_ADDITIONAL_PACKAGES, packageNames.size()) + 1;
+            ArraySet<String> allPackages = new ArraySet<>(maxCount);
+            allPackages.add(mServiceWatcher.getCurrentPackageName());
+            for (String packageName : packageNames) {
+                if (packageNames.size() >= maxCount) {
+                    return;
+                }
+
+                try {
+                    mContext.getPackageManager().getPackageInfo(packageName, MATCH_SYSTEM_ONLY);
+                    allPackages.add(packageName);
+                } catch (PackageManager.NameNotFoundException e) {
+                    Log.w(TAG, mServiceWatcher + " specified unknown additional provider package: "
+                            + packageName);
+                }
+            }
+
+            setPackageNames(allPackages);
         }
 
         // executed on binder thread
         @Override
         public void onSetEnabled(boolean enabled) {
-            LocationProviderProxy.this.setEnabled(enabled);
+            setEnabled(enabled);
         }
 
         // executed on binder thread
         @Override
         public void onSetProperties(ProviderProperties properties) {
-            LocationProviderProxy.this.setProperties(properties);
+            setProperties(properties);
         }
 
         // executed on binder thread
         @Override
         public void onReportLocation(Location location) {
-            LocationProviderProxy.this.reportLocation(location);
+            reportLocation(location);
         }
     };
 
     private final ServiceWatcher mServiceWatcher;
 
-    @GuardedBy("mProviderPackagesLock")
-    private final CopyOnWriteArrayList<String> mProviderPackages = new CopyOnWriteArrayList<>();
-
-    @GuardedBy("mRequestLock")
-    @Nullable
-    private ProviderRequest mRequest;
-    @GuardedBy("mRequestLock")
-    private WorkSource mWorkSource;
+    @Nullable private ProviderRequest mRequest;
 
     /**
      * Creates a new LocationProviderProxy and immediately begins binding to the best applicable
      * service.
      */
     @Nullable
-    public static LocationProviderProxy createAndBind(
-            Context context, LocationProviderManager locationProviderManager, String action,
+    public static LocationProviderProxy createAndBind(Context context, String action,
             int overlaySwitchResId, int defaultServicePackageNameResId,
             int initialPackageNamesResId) {
-        LocationProviderProxy proxy = new LocationProviderProxy(context, locationProviderManager,
+        LocationProviderProxy proxy = new LocationProviderProxy(context, FgThread.getHandler(),
                 action, overlaySwitchResId, defaultServicePackageNameResId,
                 initialPackageNamesResId);
         if (proxy.bind()) {
@@ -114,14 +118,13 @@
         }
     }
 
-    private LocationProviderProxy(Context context, LocationProviderManager locationProviderManager,
-            String action, int overlaySwitchResId, int defaultServicePackageNameResId,
+    private LocationProviderProxy(Context context, Handler handler, String action,
+            int overlaySwitchResId, int defaultServicePackageNameResId,
             int initialPackageNamesResId) {
-        super(context, locationProviderManager);
+        super(context, new HandlerExecutor(handler), Collections.emptySet());
 
         mServiceWatcher = new ServiceWatcher(context, TAG, action, overlaySwitchResId,
-                defaultServicePackageNameResId, initialPackageNamesResId,
-                FgThread.getHandler()) {
+                defaultServicePackageNameResId, initialPackageNamesResId, handler) {
 
             @Override
             protected void onBind() {
@@ -130,14 +133,11 @@
 
             @Override
             protected void onUnbind() {
-                resetProviderPackages(Collections.emptyList());
-                setEnabled(false);
-                setProperties(null);
+                setState(State.EMPTY_STATE);
             }
         };
 
         mRequest = null;
-        mWorkSource = new WorkSource();
     }
 
     private boolean bind() {
@@ -148,77 +148,34 @@
         ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
         if (D) Log.d(TAG, "applying state to connected service " + mServiceWatcher);
 
-        resetProviderPackages(Collections.emptyList());
+        setPackageNames(Collections.singleton(mServiceWatcher.getCurrentPackageName()));
 
         service.setLocationProviderManager(mManager);
 
-        synchronized (mRequestLock) {
-            if (mRequest != null) {
-                service.setRequest(mRequest, mWorkSource);
-            }
+        if (mRequest != null) {
+            service.setRequest(mRequest, mRequest.workSource);
         }
     }
 
     @Override
-    public List<String> getProviderPackages() {
-        synchronized (mProviderPackagesLock) {
-            return mProviderPackages;
-        }
-    }
-
-    @Override
-    public void onSetRequest(ProviderRequest request, WorkSource source) {
-        synchronized (mRequestLock) {
-            mRequest = request;
-            mWorkSource = source;
-        }
+    public void onSetRequest(ProviderRequest request) {
         mServiceWatcher.runOnBinder(binder -> {
+            mRequest = request;
             ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
-            service.setRequest(request, source);
+            service.setRequest(request, request.workSource);
         });
     }
 
     @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("service=" + mServiceWatcher);
-        synchronized (mProviderPackagesLock) {
-            if (mProviderPackages.size() > 1) {
-                pw.println("additional packages=" + mProviderPackages);
-            }
-        }
-    }
-
-    @Override
-    public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) {
+    public void onExtraCommand(int uid, int pid, String command, Bundle extras) {
         mServiceWatcher.runOnBinder(binder -> {
             ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
             service.sendExtraCommand(command, extras);
         });
     }
 
-    private void onSetAdditionalProviderPackages(List<String> packageNames) {
-        resetProviderPackages(packageNames);
-    }
-
-    private void resetProviderPackages(List<String> additionalPackageNames) {
-        ArrayList<String> permittedPackages = new ArrayList<>(additionalPackageNames.size());
-        for (String packageName : additionalPackageNames) {
-            try {
-                mContext.getPackageManager().getPackageInfo(packageName, MATCH_SYSTEM_ONLY);
-                permittedPackages.add(packageName);
-            } catch (PackageManager.NameNotFoundException e) {
-                Log.w(TAG, mServiceWatcher + " specified unknown additional provider package: "
-                        + packageName);
-            }
-        }
-
-        synchronized (mProviderPackagesLock) {
-            mProviderPackages.clear();
-            String myPackage = mServiceWatcher.getCurrentPackageName();
-            if (myPackage != null) {
-                mProviderPackages.add(myPackage);
-                mProviderPackages.addAll(permittedPackages);
-            }
-        }
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("service=" + mServiceWatcher);
     }
 }
diff --git a/services/core/java/com/android/server/location/LocationRequestStatistics.java b/services/core/java/com/android/server/location/LocationRequestStatistics.java
index b7ccb26..45c8334 100644
--- a/services/core/java/com/android/server/location/LocationRequestStatistics.java
+++ b/services/core/java/com/android/server/location/LocationRequestStatistics.java
@@ -1,8 +1,29 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.server.location;
 
 import android.os.SystemClock;
 import android.util.Log;
+import android.util.TimeUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.ArrayList;
 import java.util.HashMap;
 
 /**
@@ -17,6 +38,8 @@
     public final HashMap<PackageProviderKey, PackageStatistics> statistics
             = new HashMap<PackageProviderKey, PackageStatistics>();
 
+    public final RequestSummaryLimitedHistory history = new RequestSummaryLimitedHistory();
+
     /**
      * Signals that a package has started requesting locations.
      *
@@ -34,6 +57,7 @@
         }
         stats.startRequesting(intervalMs);
         stats.updateForeground(isForeground);
+        history.addRequest(packageName, providerName, intervalMs);
     }
 
     /**
@@ -48,6 +72,7 @@
         if (stats != null) {
             stats.stopRequesting();
         }
+        history.removeRequest(packageName, providerName);
     }
 
     /**
@@ -77,7 +102,7 @@
          */
         public final String providerName;
 
-        public PackageProviderKey(String packageName, String providerName) {
+        PackageProviderKey(String packageName, String providerName) {
             this.packageName = packageName;
             this.providerName = providerName;
         }
@@ -100,6 +125,104 @@
     }
 
     /**
+     * A data structure to hold past requests
+     */
+    public static class RequestSummaryLimitedHistory {
+        @VisibleForTesting
+        static final int MAX_SIZE = 100;
+
+        final ArrayList<RequestSummary> mList = new ArrayList<>(MAX_SIZE);
+
+        /**
+         * Append an added location request to the history
+         */
+        @VisibleForTesting
+        void addRequest(String packageName, String providerName, long intervalMs) {
+            addRequestSummary(new RequestSummary(packageName, providerName, intervalMs));
+        }
+
+        /**
+         * Append a removed location request to the history
+         */
+        @VisibleForTesting
+        void removeRequest(String packageName, String providerName) {
+            addRequestSummary(new RequestSummary(
+                    packageName, providerName, RequestSummary.REQUEST_ENDED_INTERVAL));
+        }
+
+        private void addRequestSummary(RequestSummary summary) {
+            while (mList.size() >= MAX_SIZE) {
+                mList.remove(0);
+            }
+            mList.add(summary);
+        }
+
+        /**
+         * Dump history to a printwriter (for dumpsys location)
+         */
+        public void dump(IndentingPrintWriter ipw) {
+            long systemElapsedOffsetMillis = System.currentTimeMillis()
+                    - SystemClock.elapsedRealtime();
+
+            ipw.println("Last Several Location Requests:");
+            ipw.increaseIndent();
+
+            for (RequestSummary requestSummary : mList) {
+                requestSummary.dump(ipw, systemElapsedOffsetMillis);
+            }
+
+            ipw.decreaseIndent();
+        }
+    }
+
+    /**
+     * A data structure to hold a single request
+     */
+    static class RequestSummary {
+        /**
+         * Name of package requesting location.
+         */
+        private final String mPackageName;
+        /**
+         * Name of provider being requested (e.g. "gps").
+         */
+        private final String mProviderName;
+        /**
+         * Interval Requested, or REQUEST_ENDED_INTERVAL indicating request has ended
+         */
+        private final long mIntervalMillis;
+        /**
+         * Elapsed time of request
+         */
+        private final long mElapsedRealtimeMillis;
+
+        /**
+         * Placeholder for requested ending (other values indicate request started/changed)
+         */
+        static final long REQUEST_ENDED_INTERVAL = -1;
+
+        RequestSummary(String packageName, String providerName, long intervalMillis) {
+            this.mPackageName = packageName;
+            this.mProviderName = providerName;
+            this.mIntervalMillis = intervalMillis;
+            this.mElapsedRealtimeMillis = SystemClock.elapsedRealtime();
+        }
+
+        void dump(IndentingPrintWriter ipw, long systemElapsedOffsetMillis) {
+            StringBuilder s = new StringBuilder();
+            long systemTimeMillis = systemElapsedOffsetMillis + mElapsedRealtimeMillis;
+            s.append("At ").append(TimeUtils.formatForLogging(systemTimeMillis)).append(": ")
+                    .append(mIntervalMillis == REQUEST_ENDED_INTERVAL ? "- " : "+ ")
+                    .append(String.format("%7s", mProviderName)).append(" request from ")
+                    .append(mPackageName);
+            if (mIntervalMillis != REQUEST_ENDED_INTERVAL) {
+                s.append(" at interval ").append(mIntervalMillis / 1000).append(" seconds");
+            }
+            ipw.println(s);
+        }
+    }
+
+    /**
      * Usage statistics for a package/provider pair.
      */
     public static class PackageStatistics {
diff --git a/services/core/java/com/android/server/location/LocationSettingsStore.java b/services/core/java/com/android/server/location/LocationSettingsStore.java
index f625452..0e8720e 100644
--- a/services/core/java/com/android/server/location/LocationSettingsStore.java
+++ b/services/core/java/com/android/server/location/LocationSettingsStore.java
@@ -16,6 +16,8 @@
 
 package com.android.server.location;
 
+import static android.location.LocationManager.FUSED_PROVIDER;
+import static android.location.LocationManager.PASSIVE_PROVIDER;
 import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS;
 import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST;
 import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS;
@@ -28,6 +30,7 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -248,6 +251,9 @@
                 DEFAULT_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS);
     }
 
+    /**
+     * Retrieve maximum age of the last location.
+     */
     public long getMaxLastLocationAgeMs() {
         return Settings.Global.getLong(
                 mContext.getContentResolver(),
@@ -256,6 +262,29 @@
     }
 
     /**
+     * Set a value for the deprecated LOCATION_PROVIDERS_ALLOWED setting. This is used purely for
+     * backwards compatibility for old clients, and may be removed in the future.
+     */
+    public void setLocationProviderAllowed(String provider, boolean enabled, int userId) {
+        // fused and passive provider never get public updates for legacy reasons
+        if (FUSED_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) {
+            return;
+        }
+
+        long identity = Binder.clearCallingIdentity();
+        try {
+            // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
+            Settings.Secure.putStringForUser(
+                    mContext.getContentResolver(),
+                    Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+                    (enabled ? "+" : "-") + provider,
+                    userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
      * Dump info for debugging.
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java
index 472876b..60c9fc1 100644
--- a/services/core/java/com/android/server/location/MockProvider.java
+++ b/services/core/java/com/android/server/location/MockProvider.java
@@ -19,7 +19,6 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.location.Location;
-import android.os.WorkSource;
 
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
@@ -34,41 +33,33 @@
  */
 public class MockProvider extends AbstractLocationProvider {
 
-    private boolean mEnabled;
     @Nullable private Location mLocation;
 
-    public MockProvider(Context context,
-            LocationProviderManager locationProviderManager, ProviderProperties properties) {
-        super(context, locationProviderManager);
-
-        mEnabled = true;
-        mLocation = null;
-
+    public MockProvider(Context context, ProviderProperties properties) {
+        // using a direct executor is only acceptable because this class is so simple it is trivial
+        // to verify that it does not acquire any locks or re-enter LMS from callbacks
+        super(context, Runnable::run);
         setProperties(properties);
     }
 
     /** Sets the enabled state of this mock provider. */
-    public void setEnabled(boolean enabled) {
-        mEnabled = enabled;
-        super.setEnabled(enabled);
+    public void setProviderEnabled(boolean enabled) {
+        setEnabled(enabled);
     }
 
     /** Sets the location to report for this mock provider. */
-    public void setLocation(Location l) {
-        mLocation = new Location(l);
-        if (!mLocation.isFromMockProvider()) {
-            mLocation.setIsFromMockProvider(true);
-        }
-        if (mEnabled) {
-            reportLocation(mLocation);
-        }
+    public void setProviderLocation(Location l) {
+        Location location = new Location(l);
+        location.setIsFromMockProvider(true);
+        mLocation = location;
+        reportLocation(location);
     }
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("last location=" + mLocation);
+        pw.println("last mock location=" + mLocation);
     }
 
     @Override
-    public void onSetRequest(ProviderRequest request, WorkSource source) {}
+    public void onSetRequest(ProviderRequest request) {}
 }
diff --git a/services/core/java/com/android/server/location/MockableLocationProvider.java b/services/core/java/com/android/server/location/MockableLocationProvider.java
new file mode 100644
index 0000000..f50dfe7
--- /dev/null
+++ b/services/core/java/com/android/server/location/MockableLocationProvider.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.location.Location;
+import android.os.Bundle;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.ProviderRequest;
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents a location provider that may switch between a mock implementation and a real
+ * implementation. Requires owners to provide a lock object that will be used internally and held
+ * for the duration of all listener callbacks. Owners are reponsible for ensuring this cannot lead
+ * to deadlock.
+ *
+ * In order to ensure deadlock does not occur, the owner must validate that the ONLY lock which can
+ * be held BOTH when calling into this class AND when receiving a callback from this class is the
+ * lock given to this class via the constructor. Holding any other lock is ok as long as there is no
+ * possibility that it can be obtained within both codepaths.
+ *
+ * Holding the given lock guarantees atomicity of any operations on this class for the duration.
+ *
+ * @hide
+ */
+public class MockableLocationProvider extends AbstractLocationProvider {
+
+    private final Object mOwnerLock;
+
+    @GuardedBy("mOwnerLock")
+    @Nullable private AbstractLocationProvider mProvider;
+    @GuardedBy("mOwnerLock")
+    @Nullable private AbstractLocationProvider mRealProvider;
+    @GuardedBy("mOwnerLock")
+    @Nullable private MockProvider mMockProvider;
+
+    @GuardedBy("mOwnerLock")
+    private ProviderRequest mRequest;
+
+    /**
+     * The given lock object will be held any time the listener is invoked, and may also be acquired
+     * and released during the course of invoking any public methods. Holding the given lock ensures
+     * that provider state cannot change except as result of an explicit call by the owner of the
+     * lock into this class. The client is reponsible for ensuring this cannot cause deadlock.
+     *
+     * The client should expect that it may being to receive callbacks as soon as this constructor
+     * is invoked.
+     */
+    public MockableLocationProvider(Context context, Object ownerLock, Listener listener) {
+        // using a direct executor is acceptable because all inbound calls are delegated to the
+        // actual provider implementations which will use their own executors
+        super(context, Runnable::run, Collections.emptySet());
+        mOwnerLock = ownerLock;
+        mRequest = ProviderRequest.EMPTY_REQUEST;
+
+        setListener(listener);
+    }
+
+    /**
+     * Returns the current provider implementation. May be null if there is no current
+     * implementation.
+     */
+    @Nullable
+    public AbstractLocationProvider getProvider() {
+        synchronized (mOwnerLock) {
+            return mProvider;
+        }
+    }
+
+    /**
+     * Sets the real provider implementation, replacing any previous real provider implementation.
+     * May cause an inline invocation of {@link Listener#onStateChanged(State, State)} if this
+     * results in a state change.
+     */
+    public void setRealProvider(@Nullable AbstractLocationProvider provider) {
+        synchronized (mOwnerLock) {
+            if (mRealProvider == provider) {
+                return;
+            }
+
+            mRealProvider = provider;
+            if (!isMock()) {
+                setProviderLocked(mRealProvider);
+            }
+        }
+    }
+
+    /**
+     * Sets the mock provider implementation, replacing any previous mock provider implementation.
+     * Mock implementations are always used instead of real implementations if set. May cause an
+     * inline invocation of {@link Listener#onStateChanged(State, State)} if this results in a
+     * state change.
+     */
+    public void setMockProvider(@Nullable MockProvider provider) {
+        synchronized (mOwnerLock) {
+            if (mMockProvider == provider) {
+                return;
+            }
+
+            mMockProvider = provider;
+            if (mMockProvider != null) {
+                setProviderLocked(mMockProvider);
+            } else {
+                setProviderLocked(mRealProvider);
+            }
+        }
+    }
+
+    @GuardedBy("mOwnerLock")
+    private void setProviderLocked(@Nullable AbstractLocationProvider provider) {
+        if (mProvider == provider) {
+            return;
+        }
+
+        AbstractLocationProvider oldProvider = mProvider;
+        mProvider = provider;
+
+        if (oldProvider != null) {
+            // do this after switching the provider - so even if the old provider is using a direct
+            // executor, if it re-enters this class within setRequest(), it will be ignored
+            oldProvider.setListener(null);
+            oldProvider.setRequest(ProviderRequest.EMPTY_REQUEST);
+        }
+
+        State newState;
+        if (mProvider != null) {
+            newState = mProvider.setListener(new ListenerWrapper(mProvider));
+        } else {
+            newState = State.EMPTY_STATE;
+        }
+
+        ProviderRequest oldRequest = mRequest;
+        setState(newState);
+
+        if (mProvider != null && oldRequest == mRequest) {
+            mProvider.setRequest(mRequest);
+        }
+    }
+
+    /**
+     * Returns true if the current active provider implementation is the mock implementation, and
+     * false otherwise.
+     */
+    public boolean isMock() {
+        synchronized (mOwnerLock) {
+            return mMockProvider != null && mProvider == mMockProvider;
+        }
+    }
+
+    /**
+     * Sets the mock provider implementation's enabled state. Will throw an exception if the mock
+     * provider is not currently the active implementation.
+     */
+    public void setMockProviderEnabled(boolean enabled) {
+        synchronized (mOwnerLock) {
+            Preconditions.checkState(isMock());
+            mMockProvider.setProviderEnabled(enabled);
+        }
+    }
+    /**
+     * Sets the mock provider implementation's location. Will throw an exception if the mock
+     * provider is not currently the active implementation.
+     */
+    public void setMockProviderLocation(Location location) {
+        synchronized (mOwnerLock) {
+            Preconditions.checkState(isMock());
+            mMockProvider.setProviderLocation(location);
+        }
+    }
+
+    @Override
+    public State getState() {
+        return super.getState();
+    }
+
+    /**
+     * Returns the current location request.
+     */
+    public ProviderRequest getCurrentRequest() {
+        synchronized (mOwnerLock) {
+            return mRequest;
+        }
+    }
+
+    protected void onSetRequest(ProviderRequest request) {
+        synchronized (mOwnerLock) {
+            if (request == mRequest) {
+                return;
+            }
+
+            mRequest = request;
+
+            if (mProvider != null) {
+                mProvider.setRequest(request);
+            }
+        }
+    }
+
+    protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {
+        synchronized (mOwnerLock) {
+            if (mProvider != null) {
+                mProvider.sendExtraCommand(uid, pid, command, extras);
+            }
+        }
+    }
+
+    /**
+     * Dumps the current provider implementation.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        AbstractLocationProvider provider;
+        synchronized (mOwnerLock) {
+            provider = mProvider;
+            pw.println("request=" + mRequest);
+        }
+
+        if (provider != null) {
+            // dump outside the lock in case the provider wants to acquire its own locks, and since
+            // the default provider dump behavior does not move things onto the provider thread...
+            provider.dump(fd, pw, args);
+        }
+    }
+
+    // ensures that callbacks from the incorrect provider are never visible to clients - this
+    // requires holding the owner's lock for the duration of the callback
+    private class ListenerWrapper implements Listener {
+
+        private final AbstractLocationProvider mListenerProvider;
+
+        private ListenerWrapper(AbstractLocationProvider listenerProvider) {
+            mListenerProvider = listenerProvider;
+        }
+
+        @Override
+        public final void onStateChanged(State oldState, State newState) {
+            synchronized (mOwnerLock) {
+                if (mListenerProvider != mProvider) {
+                    return;
+                }
+
+                setState(newState);
+            }
+        }
+
+        @Override
+        public final void onReportLocation(Location location) {
+            synchronized (mOwnerLock) {
+                if (mListenerProvider != mProvider) {
+                    return;
+                }
+
+                reportLocation(location);
+            }
+        }
+
+        @Override
+        public final void onReportLocation(List<Location> locations) {
+            synchronized (mOwnerLock) {
+                if (mListenerProvider != mProvider) {
+                    return;
+                }
+
+                reportLocation(locations);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java
index 639b1eb..b338770 100644
--- a/services/core/java/com/android/server/location/PassiveProvider.java
+++ b/services/core/java/com/android/server/location/PassiveProvider.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.location.Criteria;
 import android.location.Location;
-import android.os.WorkSource;
 
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
@@ -37,13 +36,22 @@
 public class PassiveProvider extends AbstractLocationProvider {
 
     private static final ProviderProperties PROPERTIES = new ProviderProperties(
-            false, false, false, false, false, false, false,
-            Criteria.POWER_LOW, Criteria.ACCURACY_COARSE);
+            /* requiresNetwork = */false,
+            /* requiresSatellite = */false,
+            /* requiresCell = */false,
+            /* hasMonetaryCost = */false,
+            /* supportsAltitude = */false,
+            /* supportsSpeed = */false,
+            /* supportsBearing = */false,
+            Criteria.POWER_LOW,
+            Criteria.ACCURACY_COARSE);
 
-    private boolean mReportLocation;
+    private volatile boolean mReportLocation;
 
-    public PassiveProvider(Context context, LocationProviderManager locationProviderManager) {
-        super(context, locationProviderManager);
+    public PassiveProvider(Context context) {
+        // using a direct executor is only acceptable because this class is so simple it is trivial
+        // to verify that it does not acquire any locks or re-enter LMS from callbacks
+        super(context, Runnable::run);
 
         mReportLocation = false;
 
@@ -52,7 +60,7 @@
     }
 
     @Override
-    public void onSetRequest(ProviderRequest request, WorkSource source) {
+    public void onSetRequest(ProviderRequest request) {
         mReportLocation = request.reportLocation;
     }
 
@@ -63,7 +71,5 @@
     }
 
     @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("report location=" + mReportLocation);
-    }
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {}
 }
diff --git a/services/core/java/com/android/server/location/UserInfoStore.java b/services/core/java/com/android/server/location/UserInfoStore.java
new file mode 100644
index 0000000..550f51c
--- /dev/null
+++ b/services/core/java/com/android/server/location/UserInfoStore.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.os.Build;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Provides accessors and listeners for all user info.
+ */
+public class UserInfoStore {
+
+    /**
+     * Listener for current user changes.
+     */
+    public interface UserChangedListener {
+        /**
+         * Called when the current user changes.
+         */
+        void onUserChanged(@UserIdInt int oldUserId, @UserIdInt int newUserId);
+    }
+
+    private final Context mContext;
+    private final CopyOnWriteArrayList<UserChangedListener> mListeners;
+
+    @GuardedBy("this")
+    @Nullable
+    private UserManager mUserManager;
+
+    @GuardedBy("this")
+    @UserIdInt
+    private int mCurrentUserId;
+
+    @GuardedBy("this")
+    @UserIdInt
+    private int mCachedParentUserId;
+    @GuardedBy("this")
+    private int[] mCachedProfileUserIds;
+
+    public UserInfoStore(Context context) {
+        mContext = context;
+        mListeners = new CopyOnWriteArrayList<>();
+
+        mCurrentUserId = UserHandle.USER_NULL;
+        mCachedParentUserId = UserHandle.USER_NULL;
+        mCachedProfileUserIds = new int[]{UserHandle.USER_NULL};
+    }
+
+    /** Called when system is ready. */
+    public synchronized void onSystemReady() {
+        if (mUserManager != null) {
+            return;
+        }
+
+        mUserManager = mContext.getSystemService(UserManager.class);
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+        intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
+        intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+
+        mContext.registerReceiverAsUser(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (action == null) {
+                    return;
+                }
+                switch (action) {
+                    case Intent.ACTION_USER_SWITCHED:
+                        int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+                                UserHandle.USER_NULL);
+                        if (userId != UserHandle.USER_NULL) {
+                            onUserChanged(userId);
+                        }
+                        break;
+                    case Intent.ACTION_MANAGED_PROFILE_ADDED:
+                    case Intent.ACTION_MANAGED_PROFILE_REMOVED:
+                        onUserProfilesChanged();
+                        break;
+                }
+            }
+        }, UserHandle.ALL, intentFilter, null, FgThread.getHandler());
+
+        mCurrentUserId = ActivityManager.getCurrentUser();
+    }
+
+    /**
+     * Adds a listener for user changed events.
+     */
+    public void addListener(UserChangedListener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Removes a listener for user changed events.
+     */
+    public void removeListener(UserChangedListener listener) {
+        mListeners.remove(listener);
+    }
+
+    private void onUserChanged(@UserIdInt int newUserId) {
+        int oldUserId;
+        synchronized (this) {
+            if (newUserId == mCurrentUserId) {
+                return;
+            }
+
+            oldUserId = mCurrentUserId;
+            mCurrentUserId = newUserId;
+        }
+
+        for (UserChangedListener listener : mListeners) {
+            listener.onUserChanged(oldUserId, newUserId);
+        }
+    }
+
+    private synchronized void onUserProfilesChanged() {
+        // this intent is only sent to the current user
+        if (mCachedParentUserId == mCurrentUserId) {
+            mCachedParentUserId = UserHandle.USER_NULL;
+            mCachedProfileUserIds = null;
+        }
+    }
+
+    /**
+     * Returns the user id of the current user.
+     */
+    @UserIdInt
+    public synchronized int getCurrentUserId() {
+        return mCurrentUserId;
+    }
+
+    /**
+     * Returns true if the given user id is either the current user or a profile of the current
+     * user.
+     */
+    public synchronized boolean isCurrentUserOrProfile(@UserIdInt int userId) {
+        return userId == mCurrentUserId || ArrayUtils.contains(
+                getProfileUserIdsForParentUser(mCurrentUserId), userId);
+    }
+
+    /**
+     * Returns the parent user id of the given user id, or the user id itself if the user id either
+     * is a parent or has no profiles.
+     */
+    @UserIdInt
+    public synchronized int getParentUserId(@UserIdInt int userId) {
+        int parentUserId;
+        if (userId == mCachedParentUserId || ArrayUtils.contains(mCachedProfileUserIds, userId)) {
+            parentUserId = mCachedParentUserId;
+        } else {
+            Preconditions.checkState(mUserManager != null);
+
+            UserInfo userInfo = mUserManager.getProfileParent(userId);
+            if (userInfo != null) {
+                parentUserId = userInfo.id;
+            } else {
+                // getProfileParent() returns null if the userId is already the parent...
+                parentUserId = userId;
+            }
+
+            // force profiles into cache
+            getProfileUserIdsForParentUser(parentUserId);
+        }
+
+        return parentUserId;
+    }
+
+    @GuardedBy("this")
+    private int[] getProfileUserIdsForParentUser(@UserIdInt int parentUserId) {
+        Preconditions.checkState(mUserManager != null);
+
+        if (Build.IS_DEBUGGABLE) {
+            Preconditions.checkArgument(mUserManager.getProfileParent(parentUserId) == null);
+        }
+
+        if (parentUserId != mCachedParentUserId) {
+            mCachedParentUserId = parentUserId;
+            mCachedProfileUserIds = mUserManager.getProfileIdsWithDisabled(parentUserId);
+        }
+
+        return mCachedProfileUserIds;
+    }
+
+    /**
+     * Dump info for debugging.
+     */
+    public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Current User: " + mCurrentUserId + " " + Arrays.toString(
+                getProfileUserIdsForParentUser(mCurrentUserId)));
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index d2e54f9..46ea9d1 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -25,11 +25,13 @@
 import android.os.ServiceManager;
 import android.os.UserManager;
 import android.util.Slog;
+import android.util.StatsLog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.RebootEscrowListener;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -109,20 +111,50 @@
     }
 
     void loadRebootEscrowDataIfAvailable() {
-        IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
-        if (rebootEscrow == null) {
+        List<UserInfo> users = mUserManager.getUsers();
+        List<UserInfo> rebootEscrowUsers = new ArrayList<>();
+        for (UserInfo user : users) {
+            if (mCallbacks.isUserSecure(user.id) && mStorage.hasRebootEscrow(user.id)) {
+                rebootEscrowUsers.add(user);
+            }
+        }
+
+        if (rebootEscrowUsers.isEmpty()) {
             return;
         }
 
-        final SecretKeySpec escrowKey;
+        SecretKeySpec escrowKey = getAndClearRebootEscrowKey();
+        if (escrowKey == null) {
+            Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
+            for (UserInfo user : users) {
+                mStorage.removeRebootEscrow(user.id);
+            }
+            StatsLog.write(StatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, false);
+            return;
+        }
+
+        boolean allUsersUnlocked = true;
+        for (UserInfo user : rebootEscrowUsers) {
+            allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey);
+        }
+        StatsLog.write(StatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, allUsersUnlocked);
+    }
+
+    private SecretKeySpec getAndClearRebootEscrowKey() {
+        IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
+        if (rebootEscrow == null) {
+            return null;
+        }
+
         try {
             byte[] escrowKeyBytes = rebootEscrow.retrieveKey();
             if (escrowKeyBytes == null) {
-                return;
+                Slog.w(TAG, "Had reboot escrow data for users, but could not retrieve key");
+                return null;
             } else if (escrowKeyBytes.length != 32) {
                 Slog.e(TAG, "IRebootEscrow returned key of incorrect size "
                         + escrowKeyBytes.length);
-                return;
+                return null;
             }
 
             // Make sure we didn't get the null key.
@@ -132,29 +164,22 @@
             }
             if (zero == 0) {
                 Slog.w(TAG, "IRebootEscrow returned an all-zeroes key");
-                return;
+                return null;
             }
 
             // Overwrite the existing key with the null key
             rebootEscrow.storeKey(new byte[32]);
 
-            escrowKey = RebootEscrowData.fromKeyBytes(escrowKeyBytes);
+            return RebootEscrowData.fromKeyBytes(escrowKeyBytes);
         } catch (RemoteException e) {
             Slog.w(TAG, "Could not retrieve escrow data");
-            return;
-        }
-
-        List<UserInfo> users = mUserManager.getUsers();
-        for (UserInfo user : users) {
-            if (mCallbacks.isUserSecure(user.id)) {
-                restoreRebootEscrowForUser(user.id, escrowKey);
-            }
+            return null;
         }
     }
 
-    private void restoreRebootEscrowForUser(@UserIdInt int userId, SecretKeySpec escrowKey) {
+    private boolean restoreRebootEscrowForUser(@UserIdInt int userId, SecretKeySpec escrowKey) {
         if (!mStorage.hasRebootEscrow(userId)) {
-            return;
+            return false;
         }
 
         try {
@@ -165,9 +190,11 @@
 
             mCallbacks.onRebootEscrowRestored(escrowData.getSpVersion(),
                     escrowData.getSyntheticPassword(), userId);
+            return true;
         } catch (IOException e) {
             Slog.w(TAG, "Could not load reboot escrow data for user " + userId, e);
         }
+        return false;
     }
 
     void callToRebootEscrowIfNeeded(@UserIdInt int userId, byte spVersion,
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index 29338ba0..52750f3 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.security.Scrypt;
 import android.security.keystore.recovery.KeyChainProtectionParams;
 import android.security.keystore.recovery.KeyChainSnapshot;
@@ -163,16 +164,28 @@
     }
 
     private void syncKeys() throws RemoteException {
+        int generation = mPlatformKeyManager.getGenerationId(mUserId);
         if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
             // Application keys for the user will not be available for sync.
             Log.w(TAG, "Credentials are not set for user " + mUserId);
-            int generation = mPlatformKeyManager.getGenerationId(mUserId);
-            mPlatformKeyManager.invalidatePlatformKey(mUserId, generation);
+            if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED
+                    || mUserId != UserHandle.USER_SYSTEM) {
+                // Only invalidate keys with legacy protection param.
+                mPlatformKeyManager.invalidatePlatformKey(mUserId, generation);
+            }
             return;
         }
         if (isCustomLockScreen()) {
-            Log.w(TAG, "Unsupported credential type " + mCredentialType + "for user " + mUserId);
-            mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId);
+            Log.w(TAG, "Unsupported credential type " + mCredentialType + " for user " + mUserId);
+            // Keys will be synced when user starts using non custom screen lock.
+            if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED
+                    || mUserId != UserHandle.USER_SYSTEM) {
+                mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId);
+            }
+            return;
+        }
+        if (mPlatformKeyManager.isDeviceLocked(mUserId) && mUserId == UserHandle.USER_SYSTEM) {
+            Log.w(TAG, "Can't sync keys for locked user " + mUserId);
             return;
         }
 
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 0ad6c2a..0761cde 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -67,8 +67,9 @@
  * @hide
  */
 public class PlatformKeyManager {
-    private static final String TAG = "PlatformKeyManager";
+    static final int MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED = 1000000;
 
+    private static final String TAG = "PlatformKeyManager";
     private static final String KEY_ALGORITHM = "AES";
     private static final int KEY_SIZE_BITS = 256;
     private static final String KEY_ALIAS_PREFIX =
@@ -131,14 +132,14 @@
 
     /**
      * Returns {@code true} if the platform key is available. A platform key won't be available if
-     * the user has not set up a lock screen.
+     * device is locked.
      *
      * @param userId The ID of the user to whose lock screen the platform key must be bound.
      *
      * @hide
      */
-    public boolean isAvailable(int userId) {
-        return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId);
+    public boolean isDeviceLocked(int userId) {
+        return mContext.getSystemService(KeyguardManager.class).isDeviceLocked(userId);
     }
 
     /**
@@ -169,7 +170,6 @@
      * @param userId The ID of the user to whose lock screen the platform key must be bound.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
      * @throws KeyStoreException if there is an error in AndroidKeyStore.
-     * @throws InsecureUserException if the user does not have a lock screen set.
      * @throws IOException if there was an issue with local database update.
      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
      *
@@ -177,13 +177,8 @@
      */
     @VisibleForTesting
     void regenerate(int userId)
-            throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException,
+            throws NoSuchAlgorithmException, KeyStoreException, IOException,
                     RemoteException {
-        if (!isAvailable(userId)) {
-            throw new InsecureUserException(String.format(
-                    Locale.US, "%d does not have a lock screen set.", userId));
-        }
-
         int generationId = getGenerationId(userId);
         int nextId;
         if (generationId == -1) {
@@ -192,6 +187,7 @@
             invalidatePlatformKey(userId, generationId);
             nextId = generationId + 1;
         }
+        generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED);
         generateAndLoadKey(userId, nextId);
     }
 
@@ -203,7 +199,6 @@
      * @throws KeyStoreException if there was an AndroidKeyStore error.
      * @throws UnrecoverableKeyException if the key could not be recovered.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
-     * @throws InsecureUserException if the user does not have a lock screen set.
      * @throws IOException if there was an issue with local database update.
      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
      *
@@ -211,7 +206,7 @@
      */
     public PlatformEncryptionKey getEncryptKey(int userId)
             throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
-                    InsecureUserException, IOException, RemoteException {
+                    IOException, RemoteException {
         init(userId);
         try {
             // Try to see if the decryption key is still accessible before using the encryption key.
@@ -234,12 +229,11 @@
      * @throws KeyStoreException if there was an AndroidKeyStore error.
      * @throws UnrecoverableKeyException if the key could not be recovered.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
-     * @throws InsecureUserException if the user does not have a lock screen set.
      *
      * @hide
      */
     private PlatformEncryptionKey getEncryptKeyInternal(int userId) throws KeyStoreException,
-           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
+            UnrecoverableKeyException, NoSuchAlgorithmException {
         int generationId = getGenerationId(userId);
         String alias = getEncryptAlias(userId, generationId);
         if (!isKeyLoaded(userId, generationId)) {
@@ -258,7 +252,6 @@
      * @throws KeyStoreException if there was an AndroidKeyStore error.
      * @throws UnrecoverableKeyException if the key could not be recovered.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
-     * @throws InsecureUserException if the user does not have a lock screen set.
      * @throws IOException if there was an issue with local database update.
      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
      *
@@ -266,7 +259,7 @@
      */
     public PlatformDecryptionKey getDecryptKey(int userId)
             throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
-                    InsecureUserException, IOException, RemoteException {
+                    IOException, RemoteException {
         init(userId);
         try {
             PlatformDecryptionKey decryptionKey = getDecryptKeyInternal(userId);
@@ -288,12 +281,11 @@
      * @throws KeyStoreException if there was an AndroidKeyStore error.
      * @throws UnrecoverableKeyException if the key could not be recovered.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
-     * @throws InsecureUserException if the user does not have a lock screen set.
      *
      * @hide
      */
     private PlatformDecryptionKey getDecryptKeyInternal(int userId) throws KeyStoreException,
-           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
+            UnrecoverableKeyException, NoSuchAlgorithmException {
         int generationId = getGenerationId(userId);
         String alias = getDecryptAlias(userId, generationId);
         if (!isKeyLoaded(userId, generationId)) {
@@ -340,13 +332,8 @@
      * @hide
      */
     void init(int userId)
-            throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException,
+            throws KeyStoreException, NoSuchAlgorithmException, IOException,
                     RemoteException {
-        if (!isAvailable(userId)) {
-            throw new InsecureUserException(String.format(
-                    Locale.US, "%d does not have a lock screen set.", userId));
-        }
-
         int generationId = getGenerationId(userId);
         if (isKeyLoaded(userId, generationId)) {
             Log.i(TAG, String.format(
@@ -363,6 +350,7 @@
             generationId++;
         }
 
+        generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED);
         generateAndLoadKey(userId, generationId);
     }
 
@@ -440,12 +428,16 @@
 
         KeyProtection.Builder decryptionKeyProtection =
                 new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
-                    .setUserAuthenticationRequired(true)
-                    .setUserAuthenticationValidityDurationSeconds(
-                            USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS)
                     .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE);
-        if (userId != UserHandle.USER_SYSTEM) {
+        // Skip UserAuthenticationRequired for main user
+        if (userId ==  UserHandle.USER_SYSTEM) {
+            decryptionKeyProtection.setUnlockedDeviceRequired(true);
+        } else {
+            // With setUnlockedDeviceRequired, KeyStore thinks that device is locked .
+            decryptionKeyProtection.setUserAuthenticationRequired(true);
+            decryptionKeyProtection.setUserAuthenticationValidityDurationSeconds(
+                            USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS);
             // Bind decryption key to secondary profile lock screen secret.
             long secureUserId = getGateKeeperService().getSecureUserId(userId);
             // TODO(b/124095438): Propagate this failure instead of silently failing.
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 383d5cf..6d97ed7 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -19,7 +19,6 @@
 import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT;
 import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED;
 import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE;
-import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER;
 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE;
 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT;
 import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
@@ -46,7 +45,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.HexDump;
-import com.android.internal.util.Preconditions;
 import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException;
 import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils;
 import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException;
@@ -76,8 +74,9 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
-import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 
 import javax.crypto.AEADBadTagException;
 
@@ -89,13 +88,14 @@
  */
 public class RecoverableKeyStoreManager {
     private static final String TAG = "RecoverableKeyStoreMgr";
+    private static final long SYNC_DELAY_MILLIS = 2000;
 
     private static RecoverableKeyStoreManager mInstance;
 
     private final Context mContext;
     private final RecoverableKeyStoreDb mDatabase;
     private final RecoverySessionStorage mRecoverySessionStorage;
-    private final ExecutorService mExecutorService;
+    private final ScheduledExecutorService mExecutorService;
     private final RecoverySnapshotListenersStorage mListenersStorage;
     private final RecoverableKeyGenerator mRecoverableKeyGenerator;
     private final RecoverySnapshotStorage mSnapshotStorage;
@@ -136,7 +136,7 @@
                     context.getApplicationContext(),
                     db,
                     new RecoverySessionStorage(),
-                    Executors.newSingleThreadExecutor(),
+                    Executors.newScheduledThreadPool(1),
                     snapshotStorage,
                     new RecoverySnapshotListenersStorage(),
                     platformKeyManager,
@@ -152,7 +152,7 @@
             Context context,
             RecoverableKeyStoreDb recoverableKeyStoreDb,
             RecoverySessionStorage recoverySessionStorage,
-            ExecutorService executorService,
+            ScheduledExecutorService executorService,
             RecoverySnapshotStorage snapshotStorage,
             RecoverySnapshotListenersStorage listenersStorage,
             PlatformKeyManager platformKeyManager,
@@ -724,8 +724,6 @@
             throw new RuntimeException(e);
         } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
-        } catch (InsecureUserException e) {
-            throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
         }
 
         try {
@@ -793,8 +791,6 @@
             throw new RuntimeException(e);
         } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
-        } catch (InsecureUserException e) {
-            throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
         }
 
         try {
@@ -915,7 +911,7 @@
             int storedHashType, @NonNull byte[] credential, int userId) {
         // So as not to block the critical path unlocking the phone, defer to another thread.
         try {
-            mExecutorService.execute(KeySyncTask.newInstance(
+            mExecutorService.schedule(KeySyncTask.newInstance(
                     mContext,
                     mDatabase,
                     mSnapshotStorage,
@@ -923,7 +919,10 @@
                     userId,
                     storedHashType,
                     credential,
-                    /*credentialUpdated=*/ false));
+                    /*credentialUpdated=*/ false),
+                    SYNC_DELAY_MILLIS,
+                    TimeUnit.MILLISECONDS
+            );
         } catch (NoSuchAlgorithmException e) {
             Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
         } catch (KeyStoreException e) {
@@ -947,7 +946,7 @@
             int userId) {
         // So as not to block the critical path unlocking the phone, defer to another thread.
         try {
-            mExecutorService.execute(KeySyncTask.newInstance(
+            mExecutorService.schedule(KeySyncTask.newInstance(
                     mContext,
                     mDatabase,
                     mSnapshotStorage,
@@ -955,7 +954,10 @@
                     userId,
                     storedHashType,
                     credential,
-                    /*credentialUpdated=*/ true));
+                    /*credentialUpdated=*/ true),
+                    SYNC_DELAY_MILLIS,
+                    TimeUnit.MILLISECONDS
+            );
         } catch (NoSuchAlgorithmException e) {
             Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
         } catch (KeyStoreException e) {
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
new file mode 100644
index 0000000..5191833
--- /dev/null
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.MediaRoute2Info;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+class BluetoothRouteProvider {
+    private static final String TAG = "BTRouteProvider";
+    private static BluetoothRouteProvider sInstance;
+
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    BluetoothA2dp mA2dpProfile;
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    BluetoothHearingAid mHearingAidProfile;
+
+    private final Context mContext;
+    private final BluetoothAdapter mBluetoothAdapter;
+    private final BluetoothRoutesUpdatedListener mListener;
+    private final Map<String, BluetoothEventReceiver> mEventReceiverMap = new HashMap<>();
+    private final IntentFilter mIntentFilter = new IntentFilter();
+    private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
+    private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
+
+    private BluetoothDevice mActiveDevice = null;
+
+    static synchronized BluetoothRouteProvider getInstance(@NonNull Context context,
+            @NonNull BluetoothRoutesUpdatedListener listener) {
+        Objects.requireNonNull(context);
+        Objects.requireNonNull(listener);
+
+        if (sInstance == null) {
+            BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+            if (btAdapter == null) {
+                return null;
+            }
+            sInstance = new BluetoothRouteProvider(context, btAdapter, listener);
+        }
+        return sInstance;
+    }
+
+    private BluetoothRouteProvider(Context context, BluetoothAdapter btAdapter,
+            BluetoothRoutesUpdatedListener listener) {
+        mContext = context;
+        mBluetoothAdapter = btAdapter;
+        mListener = listener;
+        buildBluetoothRoutes();
+
+        mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
+        mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
+
+        // Bluetooth on/off broadcasts
+        addEventReceiver(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedReceiver());
+
+        // Pairing broadcasts
+        addEventReceiver(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedReceiver());
+
+        DeviceStateChangedRecevier deviceStateChangedReceiver = new DeviceStateChangedRecevier();
+        addEventReceiver(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, deviceStateChangedReceiver);
+        addEventReceiver(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, deviceStateChangedReceiver);
+        addEventReceiver(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
+                deviceStateChangedReceiver);
+        addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED,
+                deviceStateChangedReceiver);
+
+        mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, null);
+    }
+
+    private void addEventReceiver(String action, BluetoothEventReceiver eventReceiver) {
+        mEventReceiverMap.put(action, eventReceiver);
+        mIntentFilter.addAction(action);
+    }
+
+    private void buildBluetoothRoutes() {
+        mBluetoothRoutes.clear();
+        for (BluetoothDevice device : mBluetoothAdapter.getBondedDevices()) {
+            if (device.isConnected()) {
+                BluetoothRouteInfo newBtRoute = createBluetoothRoute(device);
+                mBluetoothRoutes.put(device.getAddress(), newBtRoute);
+            }
+        }
+    }
+
+    @NonNull List<MediaRoute2Info> getBluetoothRoutes() {
+        ArrayList<MediaRoute2Info> routes = new ArrayList<>();
+        for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
+            routes.add(btRoute.route);
+        }
+        return routes;
+    }
+
+    private void notifyBluetoothRoutesUpdated() {
+        if (mListener != null) {
+            mListener.onBluetoothRoutesUpdated(getBluetoothRoutes());
+        }
+    }
+
+    private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
+        BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo();
+        newBtRoute.btDevice = device;
+        newBtRoute.route = new MediaRoute2Info.Builder(device.getAddress(), device.getName())
+                .addFeature(SystemMediaRoute2Provider.TYPE_LIVE_AUDIO)
+                .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
+                .setDescription(mContext.getResources().getText(
+                        R.string.bluetooth_a2dp_audio_route_name).toString())
+                .build();
+        newBtRoute.connectedProfiles = new SparseBooleanArray();
+        return newBtRoute;
+    }
+
+    private void setRouteConnectionStateForDevice(BluetoothDevice device,
+            @MediaRoute2Info.ConnectionState int state) {
+        if (device == null) {
+            Log.w(TAG, "setRouteConnectionStateForDevice: device shouldn't be null");
+            return;
+        }
+        BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+        if (btRoute == null) {
+            Log.w(TAG, "setRouteConnectionStateForDevice: route shouldn't be null");
+            return;
+        }
+        if (btRoute.route.getConnectionState() != state) {
+            btRoute.route = new MediaRoute2Info.Builder(btRoute.route)
+                    .setConnectionState(state).build();
+        }
+    }
+
+    interface BluetoothRoutesUpdatedListener {
+        void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes);
+    }
+
+    private class BluetoothRouteInfo {
+        public BluetoothDevice btDevice;
+        public MediaRoute2Info route;
+        public SparseBooleanArray connectedProfiles;
+    }
+
+    // These callbacks run on the main thread.
+    private final class BluetoothProfileListener implements BluetoothProfile.ServiceListener {
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            switch (profile) {
+                case BluetoothProfile.A2DP:
+                    mA2dpProfile = (BluetoothA2dp) proxy;
+                    break;
+                case BluetoothProfile.HEARING_AID:
+                    mHearingAidProfile = (BluetoothHearingAid) proxy;
+                    break;
+                default:
+                    return;
+            }
+            for (BluetoothDevice device : proxy.getConnectedDevices()) {
+                BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+                if (btRoute == null) {
+                    btRoute = createBluetoothRoute(device);
+                    mBluetoothRoutes.put(device.getAddress(), btRoute);
+                }
+                btRoute.connectedProfiles.put(profile, true);
+            }
+        }
+
+        public void onServiceDisconnected(int profile) {
+            switch (profile) {
+                case BluetoothProfile.A2DP:
+                    mA2dpProfile = null;
+                    break;
+                case BluetoothProfile.HEARING_AID:
+                    mHearingAidProfile = null;
+                    break;
+                default:
+                    return;
+            }
+        }
+    }
+    private class BluetoothBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+            BluetoothEventReceiver receiver = mEventReceiverMap.get(action);
+            if (receiver != null) {
+                receiver.onReceive(context, intent, device);
+            }
+        }
+    }
+
+    private interface BluetoothEventReceiver {
+        void onReceive(Context context, Intent intent, BluetoothDevice device);
+    }
+
+    private class AdapterStateChangedReceiver implements BluetoothEventReceiver {
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
+            if (state == BluetoothAdapter.STATE_OFF
+                    || state == BluetoothAdapter.STATE_TURNING_OFF) {
+                mBluetoothRoutes.clear();
+                notifyBluetoothRoutesUpdated();
+            } else if (state == BluetoothAdapter.STATE_ON) {
+                buildBluetoothRoutes();
+                if (!mBluetoothRoutes.isEmpty()) {
+                    notifyBluetoothRoutesUpdated();
+                }
+            }
+        }
+    }
+
+    private class BondStateChangedReceiver implements BluetoothEventReceiver {
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+                    BluetoothDevice.ERROR);
+            BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+            if (bondState == BluetoothDevice.BOND_BONDED && btRoute == null) {
+                btRoute = createBluetoothRoute(device);
+                if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) {
+                    btRoute.connectedProfiles.put(BluetoothProfile.A2DP, true);
+                }
+                if (mHearingAidProfile != null
+                        && mHearingAidProfile.getConnectedDevices().contains(device)) {
+                    btRoute.connectedProfiles.put(BluetoothProfile.HEARING_AID, true);
+                }
+                mBluetoothRoutes.put(device.getAddress(), btRoute);
+                notifyBluetoothRoutesUpdated();
+            } else if (bondState == BluetoothDevice.BOND_NONE
+                    && mBluetoothRoutes.remove(device.getAddress()) != null) {
+                notifyBluetoothRoutesUpdated();
+            }
+        }
+    }
+
+    private class DeviceStateChangedRecevier implements BluetoothEventReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            switch (intent.getAction()) {
+                case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
+                    String prevActiveDeviceAddress =
+                            (mActiveDevice == null) ? null : mActiveDevice.getAddress();
+                    String curActiveDeviceAddress =
+                            (device == null) ? null : device.getAddress();
+                    if (!TextUtils.equals(prevActiveDeviceAddress, curActiveDeviceAddress)) {
+                        if (mActiveDevice != null) {
+                            setRouteConnectionStateForDevice(mActiveDevice,
+                                    MediaRoute2Info.CONNECTION_STATE_DISCONNECTED);
+                        }
+                        if (device != null) {
+                            setRouteConnectionStateForDevice(device,
+                                    MediaRoute2Info.CONNECTION_STATE_CONNECTED);
+                        }
+                        notifyBluetoothRoutesUpdated();
+                        mActiveDevice = device;
+                    }
+                    break;
+                case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
+                    handleConnectionStateChanged(BluetoothProfile.A2DP, intent, device);
+                    break;
+            }
+        }
+
+        private void handleConnectionStateChanged(int profile, Intent intent,
+                BluetoothDevice device) {
+            int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+            BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+            if (state == BluetoothProfile.STATE_CONNECTED) {
+                if (btRoute == null) {
+                    btRoute = createBluetoothRoute(device);
+                    mBluetoothRoutes.put(device.getAddress(), btRoute);
+                    btRoute.connectedProfiles.put(profile, true);
+                    notifyBluetoothRoutesUpdated();
+                } else {
+                    btRoute.connectedProfiles.put(profile, true);
+                }
+            } else if (state == BluetoothProfile.STATE_DISCONNECTING
+                    || state == BluetoothProfile.STATE_DISCONNECTED) {
+                btRoute.connectedProfiles.delete(profile);
+                if (btRoute.connectedProfiles.size() == 0) {
+                    mBluetoothRoutes.remove(device.getAddress());
+                    if (mActiveDevice != null
+                            && TextUtils.equals(mActiveDevice.getAddress(), device.getAddress())) {
+                        mActiveDevice = null;
+                    }
+                    notifyBluetoothRoutesUpdated();
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 115155c..0d315cd 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -20,9 +20,8 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Intent;
-import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -35,7 +34,7 @@
 
     Callback mCallback;
     private volatile MediaRoute2ProviderInfo mProviderInfo;
-    private volatile List<RouteSessionInfo> mSessionInfos = Collections.emptyList();
+    private volatile List<RoutingSessionInfo> mSessionInfos = Collections.emptyList();
 
     MediaRoute2Provider(@NonNull ComponentName componentName) {
         mComponentName = Objects.requireNonNull(componentName, "Component name must not be null.");
@@ -47,16 +46,16 @@
     }
 
     public abstract void requestCreateSession(String packageName, String routeId,
-            String controlCategory, long requestId);
-    public abstract void releaseSession(int sessionId);
+            String routeType, long requestId);
+    public abstract void releaseSession(String sessionId);
 
-    public abstract void selectRoute(int sessionId, MediaRoute2Info route);
-    public abstract void deselectRoute(int sessionId, MediaRoute2Info route);
-    public abstract void transferToRoute(int sessionId, MediaRoute2Info route);
+    public abstract void selectRoute(String sessionId, String routeId);
+    public abstract void deselectRoute(String sessionId, String routeId);
+    public abstract void transferToRoute(String sessionId, String routeId);
 
-    public abstract void sendControlRequest(MediaRoute2Info route, Intent request);
-    public abstract void requestSetVolume(MediaRoute2Info route, int volume);
-    public abstract void requestUpdateVolume(MediaRoute2Info route, int delta);
+    public abstract void sendControlRequest(String routeId, Intent request);
+    public abstract void requestSetVolume(String routeId, int volume);
+    public abstract void requestUpdateVolume(String routeId, int delta);
 
     @NonNull
     public String getUniqueId() {
@@ -69,12 +68,12 @@
     }
 
     @NonNull
-    public List<RouteSessionInfo> getSessionInfos() {
+    public List<RoutingSessionInfo> getSessionInfos() {
         return mSessionInfos;
     }
 
-    void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo,
-            List<RouteSessionInfo> sessionInfos) {
+    void setProviderState(MediaRoute2ProviderInfo providerInfo,
+            List<RoutingSessionInfo> sessionInfos) {
         if (providerInfo == null) {
             mProviderInfo = null;
         } else {
@@ -82,20 +81,28 @@
                     .setUniqueId(mUniqueId)
                     .build();
         }
-        List<RouteSessionInfo> sessionInfoWithProviderId = new ArrayList<RouteSessionInfo>();
-        for (RouteSessionInfo sessionInfo : sessionInfos) {
+        List<RoutingSessionInfo> sessionInfoWithProviderId = new ArrayList<RoutingSessionInfo>();
+        for (RoutingSessionInfo sessionInfo : sessionInfos) {
             sessionInfoWithProviderId.add(
-                    new RouteSessionInfo.Builder(sessionInfo)
+                    new RoutingSessionInfo.Builder(sessionInfo)
                             .setProviderId(mUniqueId)
                             .build());
         }
         mSessionInfos = sessionInfoWithProviderId;
+    }
 
+    void notifyProviderState() {
         if (mCallback != null) {
             mCallback.onProviderStateChanged(this);
         }
     }
 
+    void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo,
+            List<RoutingSessionInfo> sessionInfos) {
+        setProviderState(providerInfo, sessionInfos);
+        notifyProviderState();
+    }
+
     public boolean hasComponentName(String packageName, String className) {
         return mComponentName.getPackageName().equals(packageName)
                 && mComponentName.getClassName().equals(className);
@@ -104,12 +111,12 @@
     public interface Callback {
         void onProviderStateChanged(@Nullable MediaRoute2Provider provider);
         void onSessionCreated(@NonNull MediaRoute2Provider provider,
-                @Nullable RouteSessionInfo sessionInfo, long requestId);
+                @Nullable RoutingSessionInfo sessionInfo, long requestId);
         // TODO: Remove this when MediaRouter2ServiceImpl notifies clients of session changes.
         void onSessionInfoChanged(@NonNull MediaRoute2Provider provider,
-                @NonNull RouteSessionInfo sessionInfo);
+                @NonNull RoutingSessionInfo sessionInfo);
         // TODO: Call this when service actually notifies of session release.
         void onSessionReleased(@NonNull MediaRoute2Provider provider,
-                @NonNull RouteSessionInfo sessionInfo);
+                @NonNull RoutingSessionInfo sessionInfo);
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index f8d8f9f..c0ad46f 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -24,10 +24,9 @@
 import android.content.ServiceConnection;
 import android.media.IMediaRoute2Provider;
 import android.media.IMediaRoute2ProviderClient;
-import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2ProviderService;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
@@ -77,17 +76,17 @@
     }
 
     @Override
-    public void requestCreateSession(String packageName, String routeId, String controlCategory,
+    public void requestCreateSession(String packageName, String routeId, String routeType,
             long requestId) {
         if (mConnectionReady) {
-            mActiveConnection.requestCreateSession(packageName, routeId, controlCategory,
+            mActiveConnection.requestCreateSession(packageName, routeId, routeType,
                     requestId);
             updateBinding();
         }
     }
 
     @Override
-    public void releaseSession(int sessionId) {
+    public void releaseSession(String sessionId) {
         if (mConnectionReady) {
             mActiveConnection.releaseSession(sessionId);
             updateBinding();
@@ -95,46 +94,46 @@
     }
 
     @Override
-    public void selectRoute(int sessionId, MediaRoute2Info route) {
+    public void selectRoute(String sessionId, String routeId) {
         if (mConnectionReady) {
-            mActiveConnection.selectRoute(sessionId, route.getId());
+            mActiveConnection.selectRoute(sessionId, routeId);
         }
     }
 
     @Override
-    public void deselectRoute(int sessionId, MediaRoute2Info route) {
+    public void deselectRoute(String sessionId, String routeId) {
         if (mConnectionReady) {
-            mActiveConnection.deselectRoute(sessionId, route.getId());
+            mActiveConnection.deselectRoute(sessionId, routeId);
         }
     }
 
     @Override
-    public void transferToRoute(int sessionId, MediaRoute2Info route) {
+    public void transferToRoute(String sessionId, String routeId) {
         if (mConnectionReady) {
-            mActiveConnection.transferToRoute(sessionId, route.getId());
+            mActiveConnection.transferToRoute(sessionId, routeId);
         }
     }
 
     @Override
-    public void sendControlRequest(MediaRoute2Info route, Intent request) {
+    public void sendControlRequest(String routeId, Intent request) {
         if (mConnectionReady) {
-            mActiveConnection.sendControlRequest(route.getId(), request);
+            mActiveConnection.sendControlRequest(routeId, request);
             updateBinding();
         }
     }
 
     @Override
-    public void requestSetVolume(MediaRoute2Info route, int volume) {
+    public void requestSetVolume(String routeId, int volume) {
         if (mConnectionReady) {
-            mActiveConnection.requestSetVolume(route.getId(), volume);
+            mActiveConnection.requestSetVolume(routeId, volume);
             updateBinding();
         }
     }
 
     @Override
-    public void requestUpdateVolume(MediaRoute2Info route, int delta) {
+    public void requestUpdateVolume(String routeId, int delta) {
         if (mConnectionReady) {
-            mActiveConnection.requestUpdateVolume(route.getId(), delta);
+            mActiveConnection.requestUpdateVolume(routeId, delta);
             updateBinding();
         }
     }
@@ -271,7 +270,7 @@
     }
 
     private void onProviderStateUpdated(Connection connection,
-            MediaRoute2ProviderInfo providerInfo, List<RouteSessionInfo> sessionInfos) {
+            MediaRoute2ProviderInfo providerInfo, List<RoutingSessionInfo> sessionInfos) {
         if (mActiveConnection != connection) {
             return;
         }
@@ -281,20 +280,20 @@
         setAndNotifyProviderState(providerInfo, sessionInfos);
     }
 
-    private void onSessionCreated(Connection connection, @Nullable RouteSessionInfo sessionInfo,
+    private void onSessionCreated(Connection connection, @Nullable RoutingSessionInfo sessionInfo,
             long requestId) {
         if (mActiveConnection != connection) {
             return;
         }
         if (sessionInfo != null) {
-            sessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+            sessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
                     .setProviderId(getUniqueId())
                     .build();
         }
         mCallback.onSessionCreated(this, sessionInfo, requestId);
     }
 
-    private void onSessionInfoChanged(Connection connection, RouteSessionInfo sessionInfo) {
+    private void onSessionInfoChanged(Connection connection, RoutingSessionInfo sessionInfo) {
         if (mActiveConnection != connection) {
             return;
         }
@@ -303,6 +302,11 @@
                     + mComponentName);
             return;
         }
+
+        sessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+                .setProviderId(getUniqueId())
+                .build();
+
         mCallback.onSessionInfoChanged(this, sessionInfo);
     }
 
@@ -346,17 +350,17 @@
             mClient.dispose();
         }
 
-        public void requestCreateSession(String packageName, String routeId, String controlCategory,
+        public void requestCreateSession(String packageName, String routeId, String routeType,
                 long requestId) {
             try {
                 mProvider.requestCreateSession(packageName, routeId,
-                        controlCategory, requestId);
+                        routeType, requestId);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "Failed to deliver request to create a session.", ex);
             }
         }
 
-        public void releaseSession(int sessionId) {
+        public void releaseSession(String sessionId) {
             try {
                 mProvider.releaseSession(sessionId);
             } catch (RemoteException ex) {
@@ -364,7 +368,7 @@
             }
         }
 
-        public void selectRoute(int sessionId, String routeId) {
+        public void selectRoute(String sessionId, String routeId) {
             try {
                 mProvider.selectRoute(sessionId, routeId);
             } catch (RemoteException ex) {
@@ -372,7 +376,7 @@
             }
         }
 
-        public void deselectRoute(int sessionId, String routeId) {
+        public void deselectRoute(String sessionId, String routeId) {
             try {
                 mProvider.deselectRoute(sessionId, routeId);
             } catch (RemoteException ex) {
@@ -380,7 +384,7 @@
             }
         }
 
-        public void transferToRoute(int sessionId, String routeId) {
+        public void transferToRoute(String sessionId, String routeId) {
             try {
                 mProvider.transferToRoute(sessionId, routeId);
             } catch (RemoteException ex) {
@@ -418,17 +422,17 @@
         }
 
         void postProviderStateUpdated(MediaRoute2ProviderInfo providerInfo,
-                List<RouteSessionInfo> sessionInfos) {
+                List<RoutingSessionInfo> sessionInfos) {
             mHandler.post(() -> onProviderStateUpdated(Connection.this,
                     providerInfo, sessionInfos));
         }
 
-        void postSessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) {
+        void postSessionCreated(@Nullable RoutingSessionInfo sessionInfo, long requestId) {
             mHandler.post(() -> onSessionCreated(Connection.this, sessionInfo,
                     requestId));
         }
 
-        void postSessionInfoChanged(RouteSessionInfo sessionInfo) {
+        void postSessionInfoChanged(RoutingSessionInfo sessionInfo) {
             mHandler.post(() -> onSessionInfoChanged(Connection.this, sessionInfo));
         }
     }
@@ -446,7 +450,7 @@
 
         @Override
         public void updateState(MediaRoute2ProviderInfo providerInfo,
-                List<RouteSessionInfo> sessionInfos) {
+                List<RoutingSessionInfo> sessionInfos) {
             Connection connection = mConnectionRef.get();
             if (connection != null) {
                 connection.postProviderStateUpdated(providerInfo, sessionInfos);
@@ -454,7 +458,7 @@
         }
 
         @Override
-        public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) {
+        public void notifySessionCreated(@Nullable RoutingSessionInfo sessionInfo, long requestId) {
             Connection connection = mConnectionRef.get();
             if (connection != null) {
                 connection.postSessionCreated(sessionInfo, requestId);
@@ -462,7 +466,7 @@
         }
 
         @Override
-        public void notifySessionInfoChanged(RouteSessionInfo sessionInfo) {
+        public void notifySessionInfoChanged(RoutingSessionInfo sessionInfo) {
             Connection connection = mConnectionRef.get();
             if (connection != null) {
                 connection.postSessionInfoChanged(sessionInfo);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 562a720..c48c90d 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -16,6 +16,9 @@
 
 package com.android.server.media;
 
+import static android.media.MediaRouter2Utils.getOriginalId;
+import static android.media.MediaRouter2Utils.getProviderId;
+
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.annotation.NonNull;
@@ -28,7 +31,8 @@
 import android.media.IMediaRouter2Manager;
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -174,18 +178,18 @@
     }
 
     public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route,
-            String controlCategory, int requestId) {
+            String routeFeature, 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");
+        if (TextUtils.isEmpty(routeFeature)) {
+            throw new IllegalArgumentException("routeFeature must not be empty");
         }
 
         final long token = Binder.clearCallingIdentity();
 
         try {
             synchronized (mLock) {
-                requestCreateSessionLocked(client, route, controlCategory, requestId);
+                requestCreateSessionLocked(client, route, routeFeature, requestId);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -196,6 +200,9 @@
             MediaRoute2Info route) {
         Objects.requireNonNull(client, "client must not be null");
         Objects.requireNonNull(route, "route must not be null");
+        if (TextUtils.isEmpty(uniqueSessionId)) {
+            throw new IllegalArgumentException("uniqueSessionId must not be empty");
+        }
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -212,6 +219,9 @@
             MediaRoute2Info route) {
         Objects.requireNonNull(client, "client must not be null");
         Objects.requireNonNull(route, "route must not be null");
+        if (TextUtils.isEmpty(uniqueSessionId)) {
+            throw new IllegalArgumentException("uniqueSessionId must not be empty");
+        }
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -227,6 +237,9 @@
             MediaRoute2Info route) {
         Objects.requireNonNull(client, "client must not be null");
         Objects.requireNonNull(route, "route must not be null");
+        if (TextUtils.isEmpty(uniqueSessionId)) {
+            throw new IllegalArgumentException("uniqueSessionId must not be empty");
+        }
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -240,6 +253,9 @@
 
     public void releaseSession(IMediaRouter2Client client, String uniqueSessionId) {
         Objects.requireNonNull(client, "client must not be null");
+        if (TextUtils.isEmpty(uniqueSessionId)) {
+            throw new IllegalArgumentException("uniqueSessionId must not be empty");
+        }
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -267,16 +283,16 @@
         }
     }
 
-    public void setControlCategories(@NonNull IMediaRouter2Client client,
-            @NonNull List<String> categories) {
+    public void setDiscoveryRequest2(@NonNull IMediaRouter2Client client,
+            @NonNull RouteDiscoveryPreference preference) {
         Objects.requireNonNull(client, "client must not be null");
-        Objects.requireNonNull(categories, "categories must not be null");
+        Objects.requireNonNull(preference, "preference must not be null");
 
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
                 Client2Record clientRecord = mAllClientRecords.get(client.asBinder());
-                setControlCategoriesLocked(clientRecord, categories);
+                setDiscoveryRequestLocked(clientRecord, preference);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -354,7 +370,7 @@
     }
 
     @NonNull
-    public List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
+    public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
@@ -434,7 +450,7 @@
     }
 
     private void requestCreateSessionLocked(@NonNull IMediaRouter2Client client,
-            @NonNull MediaRoute2Info route, @NonNull String controlCategory, long requestId) {
+            @NonNull MediaRoute2Info route, @NonNull String routeFeature, long requestId) {
         final IBinder binder = client.asBinder();
         final Client2Record clientRecord = mAllClientRecords.get(binder);
 
@@ -447,7 +463,7 @@
             clientRecord.mUserRecord.mHandler.sendMessage(
                     obtainMessage(UserHandler::requestCreateSessionOnHandler,
                             clientRecord.mUserRecord.mHandler,
-                            clientRecord, route, controlCategory, requestId));
+                            clientRecord, route, routeFeature, requestId));
         }
     }
 
@@ -502,13 +518,14 @@
         }
     }
 
-    private void setControlCategoriesLocked(Client2Record clientRecord, List<String> categories) {
+    private void setDiscoveryRequestLocked(Client2Record clientRecord,
+            RouteDiscoveryPreference discoveryRequest) {
         if (clientRecord != null) {
-            if (clientRecord.mControlCategories.equals(categories)) {
+            if (clientRecord.mDiscoveryPreference.equals(discoveryRequest)) {
                 return;
             }
 
-            clientRecord.mControlCategories = categories;
+            clientRecord.mDiscoveryPreference = discoveryRequest;
             clientRecord.mUserRecord.mHandler.sendMessage(
                     obtainMessage(UserHandler::updateClientUsage,
                             clientRecord.mUserRecord.mHandler, clientRecord));
@@ -605,9 +622,9 @@
             }
             long uniqueRequestId = toUniqueRequestId(managerRecord.mClientId, requestId);
             if (clientRecord != null && managerRecord.mTrusted) {
-                //TODO: select category properly
+                //TODO: select route feature properly
                 requestCreateSessionLocked(clientRecord.mClient, route,
-                        route.getSupportedCategories().get(0), uniqueRequestId);
+                        route.getFeatures().get(0), uniqueRequestId);
             }
         }
     }
@@ -636,7 +653,7 @@
         }
     }
 
-    private List<RouteSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) {
+    private List<RoutingSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) {
         final IBinder binder = manager.asBinder();
         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
 
@@ -644,7 +661,7 @@
             return Collections.emptyList();
         }
 
-        List<RouteSessionInfo> sessionInfos = new ArrayList<>();
+        List<RoutingSessionInfo> sessionInfos = new ArrayList<>();
         for (MediaRoute2Provider provider : managerRecord.mUserRecord.mHandler.mMediaProviders) {
             sessionInfos.addAll(provider.getSessionInfos());
         }
@@ -725,7 +742,7 @@
         public final boolean mTrusted;
         public final int mClientId;
 
-        public List<String> mControlCategories;
+        public RouteDiscoveryPreference mDiscoveryPreference;
         public boolean mIsManagerSelecting;
         public MediaRoute2Info mSelectingRoute;
         public MediaRoute2Info mSelectedRoute;
@@ -735,7 +752,7 @@
             mUserRecord = userRecord;
             mPackageName = packageName;
             mSelectRouteSequenceNumbers = new ArrayList<>();
-            mControlCategories = Collections.emptyList();
+            mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
             mClient = client;
             mUid = uid;
             mPid = pid;
@@ -859,20 +876,21 @@
 
         @Override
         public void onSessionCreated(@NonNull MediaRoute2Provider provider,
-                @Nullable RouteSessionInfo sessionInfo, long requestId) {
+                @Nullable RoutingSessionInfo sessionInfo, long requestId) {
             sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreatedOnHandler,
                     this, provider, sessionInfo, requestId));
         }
 
         @Override
         public void onSessionInfoChanged(@NonNull MediaRoute2Provider provider,
-                @NonNull RouteSessionInfo sessionInfo) {
+                @NonNull RoutingSessionInfo sessionInfo) {
             sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionInfoChangedOnHandler,
                     this, provider, sessionInfo));
         }
 
         @Override
-        public void onSessionReleased(MediaRoute2Provider provider, RouteSessionInfo sessionInfo) {
+        public void onSessionReleased(MediaRoute2Provider provider,
+                RoutingSessionInfo sessionInfo) {
             sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionReleasedOnHandler,
                     this, provider, sessionInfo));
         }
@@ -915,7 +933,7 @@
                         Slog.w(TAG, "Ignoring invalid route : " + route);
                         continue;
                     }
-                    MediaRoute2Info prevRoute = prevInfo.getRoute(route.getId());
+                    MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId());
 
                     if (prevRoute != null) {
                         if (!Objects.equals(prevRoute, route)) {
@@ -961,7 +979,7 @@
         }
 
         private void requestCreateSessionOnHandler(Client2Record clientRecord,
-                MediaRoute2Info route, String controlCategory, long requestId) {
+                MediaRoute2Info route, String routeFeature, long requestId) {
 
             final MediaRoute2Provider provider = findProvider(route.getProviderId());
             if (provider == null) {
@@ -971,20 +989,20 @@
                 return;
             }
 
-            if (!route.getSupportedCategories().contains(controlCategory)) {
+            if (!route.getFeatures().contains(routeFeature)) {
                 Slog.w(TAG, "Ignoring session creation request since the given route=" + route
-                        + " doesn't support the given category=" + controlCategory);
+                        + " doesn't support the given feature=" + routeFeature);
                 notifySessionCreationFailed(clientRecord, toClientRequestId(requestId));
                 return;
             }
 
             // TODO: Apply timeout for each request (How many seconds should we wait?)
             SessionCreationRequest request = new SessionCreationRequest(
-                    clientRecord, route, controlCategory, requestId);
+                    clientRecord, route, routeFeature, requestId);
             mSessionCreationRequests.add(request);
 
-            provider.requestCreateSession(clientRecord.mPackageName, route.getId(),
-                    controlCategory, requestId);
+            provider.requestCreateSession(clientRecord.mPackageName, route.getOriginalId(),
+                    routeFeature, requestId);
         }
 
         private void selectRouteOnHandler(@NonNull Client2Record clientRecord,
@@ -1000,7 +1018,7 @@
             if (provider == null) {
                 return;
             }
-            provider.selectRoute(RouteSessionInfo.getSessionId(uniqueSessionId), route);
+            provider.selectRoute(getOriginalId(uniqueSessionId), route.getOriginalId());
         }
 
         private void deselectRouteOnHandler(@NonNull Client2Record clientRecord,
@@ -1016,7 +1034,7 @@
             if (provider == null) {
                 return;
             }
-            provider.deselectRoute(RouteSessionInfo.getSessionId(uniqueSessionId), route);
+            provider.deselectRoute(getOriginalId(uniqueSessionId), route.getOriginalId());
         }
 
         private void transferToRouteOnHandler(@NonNull Client2Record clientRecord,
@@ -1032,7 +1050,8 @@
             if (provider == null) {
                 return;
             }
-            provider.transferToRoute(RouteSessionInfo.getSessionId(uniqueSessionId), route);
+            provider.transferToRoute(getOriginalId(uniqueSessionId),
+                    route.getOriginalId());
         }
 
         private boolean checkArgumentsForSessionControl(@NonNull Client2Record clientRecord,
@@ -1063,9 +1082,9 @@
                 return false;
             }
 
-            final Integer sessionId = RouteSessionInfo.getSessionId(uniqueSessionId);
+            final String sessionId = getOriginalId(uniqueSessionId);
             if (sessionId == null) {
-                Slog.w(TAG, "Failed to get int session id from unique session id. "
+                Slog.w(TAG, "Failed to get original session id from unique session id. "
                         + "uniqueSessionId=" + uniqueSessionId);
                 return false;
             }
@@ -1088,14 +1107,14 @@
                 return;
             }
 
-            final String providerId = RouteSessionInfo.getProviderId(uniqueSessionId);
+            final String providerId = getProviderId(uniqueSessionId);
             if (providerId == null) {
                 Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. "
                         + "uniqueSessionId=" + uniqueSessionId);
                 return;
             }
 
-            final Integer sessionId = RouteSessionInfo.getSessionId(uniqueSessionId);
+            final String sessionId = getOriginalId(uniqueSessionId);
             if (sessionId == null) {
                 Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. "
                         + "uniqueSessionId=" + uniqueSessionId + " providerId=" + providerId);
@@ -1113,7 +1132,7 @@
         }
 
         private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider,
-                @Nullable RouteSessionInfo sessionInfo, long requestId) {
+                @Nullable RoutingSessionInfo sessionInfo, long requestId) {
             SessionCreationRequest matchingRequest = null;
 
             for (SessionCreationRequest request : mSessionCreationRequests) {
@@ -1141,16 +1160,16 @@
             }
 
             String originalRouteId = matchingRequest.mRoute.getId();
-            String originalCategory = matchingRequest.mControlCategory;
+            String originalRouteFeature = matchingRequest.mRouteFeature;
             Client2Record client2Record = matchingRequest.mClientRecord;
 
             if (!sessionInfo.getSelectedRoutes().contains(originalRouteId)
-                    || !TextUtils.equals(originalCategory,
-                        sessionInfo.getControlCategory())) {
+                    || !TextUtils.equals(originalRouteFeature,
+                        sessionInfo.getRouteFeature())) {
                 Slog.w(TAG, "Created session doesn't match the original request."
                         + " originalRouteId=" + originalRouteId
-                        + ", originalCategory=" + originalCategory + ", requestId=" + requestId
-                        + ", sessionInfo=" + sessionInfo);
+                        + ", originalRouteFeature=" + originalRouteFeature
+                        + ", requestId=" + requestId + ", sessionInfo=" + sessionInfo);
                 notifySessionCreationFailed(matchingRequest.mClientRecord,
                         toClientRequestId(requestId));
                 return;
@@ -1159,46 +1178,39 @@
             // Succeeded
             notifySessionCreated(matchingRequest.mClientRecord,
                     sessionInfo, toClientRequestId(requestId));
-            mSessionToClientMap.put(sessionInfo.getUniqueSessionId(), client2Record);
+            mSessionToClientMap.put(sessionInfo.getId(), client2Record);
             // TODO: Tell managers for the session creation
         }
 
         private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
-                @NonNull RouteSessionInfo sessionInfo) {
-            RouteSessionInfo sessionInfoWithProviderId = new RouteSessionInfo.Builder(sessionInfo)
-                    .setProviderId(provider.getUniqueId())
-                    .build();
+                @NonNull RoutingSessionInfo sessionInfo) {
 
             Client2Record client2Record = mSessionToClientMap.get(
-                    sessionInfoWithProviderId.getUniqueSessionId());
+                    sessionInfo.getId());
             if (client2Record == null) {
-                Slog.w(TAG, "No matching client found for session=" + sessionInfoWithProviderId);
+                Slog.w(TAG, "No matching client found for session=" + sessionInfo);
                 // TODO: Tell managers for the session update
                 return;
             }
-            notifySessionInfoChanged(client2Record, sessionInfoWithProviderId);
+            notifySessionInfoChanged(client2Record, sessionInfo);
             // TODO: Tell managers for the session update
         }
 
         private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
-                @NonNull RouteSessionInfo sessionInfo) {
-            RouteSessionInfo sessionInfoWithProviderId = new RouteSessionInfo.Builder(sessionInfo)
-                    .setProviderId(provider.getUniqueId())
-                    .build();
+                @NonNull RoutingSessionInfo sessionInfo) {
 
-            Client2Record client2Record = mSessionToClientMap.get(
-                    sessionInfoWithProviderId.getUniqueSessionId());
+            Client2Record client2Record = mSessionToClientMap.get(sessionInfo.getId());
             if (client2Record == null) {
-                Slog.w(TAG, "No matching client found for session=" + sessionInfoWithProviderId);
+                Slog.w(TAG, "No matching client found for session=" + sessionInfo);
                 // TODO: Tell managers for the session release
                 return;
             }
-            notifySessionReleased(client2Record, sessionInfoWithProviderId);
+            notifySessionReleased(client2Record, sessionInfo);
             // TODO: Tell managers for the session release
         }
 
-        private void notifySessionCreated(Client2Record clientRecord, RouteSessionInfo sessionInfo,
-                int requestId) {
+        private void notifySessionCreated(Client2Record clientRecord,
+                RoutingSessionInfo sessionInfo, int requestId) {
             try {
                 clientRecord.mClient.notifySessionCreated(sessionInfo, requestId);
             } catch (RemoteException ex) {
@@ -1217,7 +1229,7 @@
         }
 
         private void notifySessionInfoChanged(Client2Record clientRecord,
-                RouteSessionInfo sessionInfo) {
+                RoutingSessionInfo sessionInfo) {
             try {
                 clientRecord.mClient.notifySessionInfoChanged(sessionInfo);
             } catch (RemoteException ex) {
@@ -1227,7 +1239,7 @@
         }
 
         private void notifySessionReleased(Client2Record clientRecord,
-                RouteSessionInfo sessionInfo) {
+                RoutingSessionInfo sessionInfo) {
             try {
                 clientRecord.mClient.notifySessionReleased(sessionInfo);
             } catch (RemoteException ex) {
@@ -1239,21 +1251,21 @@
         private void sendControlRequest(MediaRoute2Info route, Intent request) {
             final MediaRoute2Provider provider = findProvider(route.getProviderId());
             if (provider != null) {
-                provider.sendControlRequest(route, request);
+                provider.sendControlRequest(route.getOriginalId(), request);
             }
         }
 
         private void requestSetVolume(MediaRoute2Info route, int volume) {
             final MediaRoute2Provider provider = findProvider(route.getProviderId());
             if (provider != null) {
-                provider.requestSetVolume(route, volume);
+                provider.requestSetVolume(route.getOriginalId(), volume);
             }
         }
 
         private void requestUpdateVolume(MediaRoute2Info route, int delta) {
             final MediaRoute2Provider provider = findProvider(route.getProviderId());
             if (provider != null) {
-                provider.requestUpdateVolume(route, delta);
+                provider.requestUpdateVolume(route.getOriginalId(), delta);
             }
         }
 
@@ -1401,8 +1413,8 @@
                 try {
                     manager.notifyRouteSelected(clientRecord.mPackageName,
                             clientRecord.mSelectedRoute);
-                    manager.notifyControlCategoriesChanged(clientRecord.mPackageName,
-                            clientRecord.mControlCategories);
+                    manager.notifyPreferredFeaturesChanged(clientRecord.mPackageName,
+                            clientRecord.mDiscoveryPreference.getPreferredFeatures());
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "Failed to update client usage. Manager probably died.", ex);
                 }
@@ -1421,15 +1433,15 @@
         final class SessionCreationRequest {
             public final Client2Record mClientRecord;
             public final MediaRoute2Info mRoute;
-            public final String mControlCategory;
+            public final String mRouteFeature;
             public final long mRequestId;
 
             SessionCreationRequest(@NonNull Client2Record clientRecord,
                     @NonNull MediaRoute2Info route,
-                    @NonNull String controlCategory, long requestId) {
+                    @NonNull String routeFeature, long requestId) {
                 mClientRecord = clientRecord;
                 mRoute = route;
-                mControlCategory = controlCategory;
+                mRouteFeature = routeFeature;
                 mRequestId = requestId;
             }
         }
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index d77f43b..b7aa484 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -39,7 +39,8 @@
 import android.media.MediaRouterClientState;
 import android.media.RemoteDisplayState;
 import android.media.RemoteDisplayState.RemoteDisplayInfo;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -459,8 +460,8 @@
     // Binder call
     @Override
     public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route,
-            String controlCategory, int requestId) {
-        mService2.requestCreateSession(client, route, controlCategory, requestId);
+            String routeType, int requestId) {
+        mService2.requestCreateSession(client, route, routeType, requestId);
     }
 
     // Binder call
@@ -519,8 +520,8 @@
     }
     // Binder call
     @Override
-    public void setControlCategories(IMediaRouter2Client client, List<String> categories) {
-        mService2.setControlCategories(client, categories);
+    public void setDiscoveryRequest2(IMediaRouter2Client client, RouteDiscoveryPreference request) {
+        mService2.setDiscoveryRequest2(client, request);
     }
 
     // Binder call
@@ -551,7 +552,7 @@
 
     // Binder call
     @Override
-    public List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
+    public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
         return mService2.getActiveSessions(manager);
     }
 
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
new file mode 100644
index 0000000..f3241ee
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.media.MediaController2;
+import android.media.Session2CommandGroup;
+import android.media.Session2Token;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+
+/**
+ * Keeps the record of {@link Session2Token} helps to send command to the corresponding session.
+ */
+// TODO(jaewan): Do not call service method directly -- introduce listener instead.
+public class MediaSession2Record implements MediaSessionRecordImpl {
+    private static final String TAG = "MediaSession2Record";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final Session2Token mSessionToken;
+    @GuardedBy("mLock")
+    private final HandlerExecutor mHandlerExecutor;
+    @GuardedBy("mLock")
+    private final MediaController2 mController;
+    @GuardedBy("mLock")
+    private final MediaSessionService mService;
+    @GuardedBy("mLock")
+    private boolean mIsConnected;
+
+    public MediaSession2Record(Session2Token sessionToken, MediaSessionService service,
+            Looper handlerLooper) {
+        mSessionToken = sessionToken;
+        mService = service;
+        mHandlerExecutor = new HandlerExecutor(new Handler(handlerLooper));
+        mController = new MediaController2.Builder(service.getContext(), sessionToken)
+                .setControllerCallback(mHandlerExecutor, new Controller2Callback())
+                .build();
+    }
+
+    @Override
+    public String getPackageName() {
+        return mSessionToken.getPackageName();
+    }
+
+    public Session2Token getSession2Token() {
+        return mSessionToken;
+    }
+
+    @Override
+    public int getUid() {
+        return mSessionToken.getUid();
+    }
+
+    @Override
+    public int getUserId() {
+        return UserHandle.getUserId(mSessionToken.getUid());
+    }
+
+    @Override
+    public boolean isSystemPriority() {
+        // System priority session is currently only allowed for telephony, and it's OK to stick to
+        // the media1 API at this moment.
+        return false;
+    }
+
+    @Override
+    public void adjustVolume(String packageName, String opPackageName, int pid, int uid,
+            boolean asSystemService, int direction, int flags, boolean useSuggested) {
+        // TODO(jaewan): Add API to adjust volume.
+    }
+
+    @Override
+    public boolean isActive() {
+        synchronized (mLock) {
+            return mIsConnected;
+        }
+    }
+
+    @Override
+    public boolean checkPlaybackActiveState(boolean expected) {
+        synchronized (mLock) {
+            return mIsConnected && mController.isPlaybackActive() == expected;
+        }
+    }
+
+    @Override
+    public boolean isPlaybackTypeLocal() {
+        // TODO(jaewan): Implement -- need API to know whether the playback is remote or local.
+        return true;
+    }
+
+    @Override
+    public void close() {
+        synchronized (mLock) {
+            // Call close regardless of the mIsAvailable. This may be called when it's not yet
+            // connected.
+            mController.close();
+        }
+    }
+
+    @Override
+    public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
+            KeyEvent ke, int sequenceId, ResultReceiver cb) {
+        // TODO(jaewan): Implement.
+        return false;
+    }
+
+    @Override
+    public void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "token=" + mSessionToken);
+        pw.println(prefix + "controller=" + mController);
+
+        final String indent = prefix + "  ";
+        pw.println(indent + "playbackActive=" + mController.isPlaybackActive());
+    }
+
+    @Override
+    public String toString() {
+        // TODO(jaewan): Also add getId().
+        return getPackageName() + " (userId=" + getUserId() + ")";
+    }
+
+    private class Controller2Callback extends MediaController2.ControllerCallback {
+        @Override
+        public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
+            if (DEBUG) {
+                Log.d(TAG, "connected to " + mSessionToken + ", allowed=" + allowedCommands);
+            }
+            synchronized (mLock) {
+                mIsConnected = true;
+            }
+            mService.onSessionActiveStateChanged(MediaSession2Record.this);
+        }
+
+        @Override
+        public void onDisconnected(MediaController2 controller) {
+            if (DEBUG) {
+                Log.d(TAG, "disconnected from " + mSessionToken);
+            }
+            synchronized (mLock) {
+                mIsConnected = false;
+            }
+            mService.onSessionDied(MediaSession2Record.this);
+        }
+
+        @Override
+        public void onPlaybackActiveChanged(MediaController2 controller, boolean playbackActive) {
+            if (DEBUG) {
+                Log.d(TAG, "playback active changed, " + mSessionToken + ", active="
+                        + playbackActive);
+            }
+            mService.onSessionPlaybackStateChanged(MediaSession2Record.this, playbackActive);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index aa24ed2..df115d0 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -56,13 +56,15 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
  * This is the system implementation of a Session. Apps will interact with the
  * MediaSession wrapper class instead.
  */
-public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable {
+// TODO(jaewan): Do not call service method directly -- introduce listener instead.
+public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl {
     private static final String TAG = "MediaSessionRecord";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -72,6 +74,24 @@
      */
     private static final int OPTIMISTIC_VOLUME_TIMEOUT = 1000;
 
+    /**
+     * These are states that usually indicate the user took an action and should
+     * bump priority regardless of the old state.
+     */
+    private static final List<Integer> ALWAYS_PRIORITY_STATES = Arrays.asList(
+            PlaybackState.STATE_FAST_FORWARDING,
+            PlaybackState.STATE_REWINDING,
+            PlaybackState.STATE_SKIPPING_TO_PREVIOUS,
+            PlaybackState.STATE_SKIPPING_TO_NEXT);
+    /**
+     * These are states that usually indicate the user took an action if they
+     * were entered from a non-priority state.
+     */
+    private static final List<Integer> TRANSITION_PRIORITY_STATES = Arrays.asList(
+            PlaybackState.STATE_BUFFERING,
+            PlaybackState.STATE_CONNECTING,
+            PlaybackState.STATE_PLAYING);
+
     private final MessageHandler mHandler;
 
     private final int mOwnerPid;
@@ -170,6 +190,7 @@
      *
      * @return Info that identifies this session.
      */
+    @Override
     public String getPackageName() {
         return mPackageName;
     }
@@ -188,6 +209,7 @@
      *
      * @return The UID for this session.
      */
+    @Override
     public int getUid() {
         return mOwnerUid;
     }
@@ -197,6 +219,7 @@
      *
      * @return The user id for this session.
      */
+    @Override
     public int getUserId() {
         return mUserId;
     }
@@ -207,6 +230,7 @@
      *
      * @return True if this is a system priority session, false otherwise
      */
+    @Override
     public boolean isSystemPriority() {
         return (mFlags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0;
     }
@@ -220,7 +244,6 @@
      * @param opPackageName The op package that made the original volume request.
      * @param pid The pid that made the original volume request.
      * @param uid The uid that made the original volume request.
-     * @param caller caller binder. can be {@code null} if it's from the volume key.
      * @param asSystemService {@code true} if the event sent to the session as if it was come from
      *          the system service instead of the app process. This helps sessions to distinguish
      *          between the key injection by the app and key events from the hardware devices.
@@ -318,9 +341,13 @@
 
     /**
      * Check if this session has been set to active by the app.
+     * <p>
+     * It's not used to prioritize sessions for dispatching media keys since API 26, but still used
+     * to filter session list in MediaSessionManager#getActiveSessions().
      *
      * @return True if the session is active, false otherwise.
      */
+    @Override
     public boolean isActive() {
         return mIsActive && !mDestroyed;
     }
@@ -333,6 +360,7 @@
      * @param expected True if playback is expected to be active. false otherwise.
      * @return True if the session's playback matches with the expectation. false otherwise.
      */
+    @Override
     public boolean checkPlaybackActiveState(boolean expected) {
         if (mPlaybackState == null) {
             return false;
@@ -345,13 +373,14 @@
      *
      * @return {@code true} if the playback is local.
      */
-    public boolean isPlaybackLocal() {
+    @Override
+    public boolean isPlaybackTypeLocal() {
         return mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
     }
 
     @Override
     public void binderDied() {
-        mService.sessionDied(this);
+        mService.onSessionDied(this);
     }
 
     /**
@@ -383,7 +412,7 @@
      * @param sequenceId (optional) sequence id. Use this only when a wake lock is needed.
      * @param cb (optional) result receiver to receive callback. Use this only when a wake lock is
      *           needed.
-     * @return {@code true} if the attempt to send media button was successfuly.
+     * @return {@code true} if the attempt to send media button was successfully.
      *         {@code false} otherwise.
      */
     public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
@@ -392,6 +421,7 @@
                 cb);
     }
 
+    @Override
     public void dump(PrintWriter pw, String prefix) {
         pw.println(prefix + mTag + " " + this);
 
@@ -712,7 +742,7 @@
         public void destroySession() throws RemoteException {
             final long token = Binder.clearCallingIdentity();
             try {
-                mService.destroySession(MediaSessionRecord.this);
+                mService.onSessionDied(MediaSessionRecord.this);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -734,7 +764,7 @@
             mIsActive = active;
             final long token = Binder.clearCallingIdentity();
             try {
-                mService.updateSession(MediaSessionRecord.this);
+                mService.onSessionActiveStateChanged(MediaSessionRecord.this);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -801,12 +831,16 @@
                     ? PlaybackState.STATE_NONE : mPlaybackState.getState();
             int newState = state == null
                     ? PlaybackState.STATE_NONE : state.getState();
+            boolean shouldUpdatePriority = ALWAYS_PRIORITY_STATES.contains(newState)
+                    || (!TRANSITION_PRIORITY_STATES.contains(oldState)
+                    && TRANSITION_PRIORITY_STATES.contains(newState));
             synchronized (mLock) {
                 mPlaybackState = state;
             }
             final long token = Binder.clearCallingIdentity();
             try {
-                mService.onSessionPlaystateChanged(MediaSessionRecord.this, oldState, newState);
+                mService.onSessionPlaybackStateChanged(
+                        MediaSessionRecord.this, shouldUpdatePriority);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
new file mode 100644
index 0000000..2cde89a7
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.media.AudioManager;
+import android.os.ResultReceiver;
+import android.view.KeyEvent;
+
+import java.io.PrintWriter;
+
+/**
+ * Common interfaces between {@link MediaSessionRecord} and {@link MediaSession2Record}.
+ */
+public interface MediaSessionRecordImpl extends AutoCloseable {
+
+    /**
+     * Get the info for this session.
+     *
+     * @return Info that identifies this session.
+     */
+    String getPackageName();
+
+    /**
+     * Get the UID this session was created for.
+     *
+     * @return The UID for this session.
+     */
+    int getUid();
+
+    /**
+     * Get the user id this session was created for.
+     *
+     * @return The user id for this session.
+     */
+    int getUserId();
+
+    /**
+     * Check if this session has system priorty and should receive media buttons
+     * before any other sessions.
+     *
+     * @return True if this is a system priority session, false otherwise
+     */
+    boolean isSystemPriority();
+
+    /**
+     * Send a volume adjustment to the session owner. Direction must be one of
+     * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
+     * {@link AudioManager#ADJUST_SAME}.
+     *
+     * @param packageName The package that made the original volume request.
+     * @param opPackageName The op package that made the original volume request.
+     * @param pid The pid that made the original volume request.
+     * @param uid The uid that made the original volume request.
+     * @param asSystemService {@code true} if the event sent to the session as if it was come from
+     *          the system service instead of the app process. This helps sessions to distinguish
+     *          between the key injection by the app and key events from the hardware devices.
+     *          Should be used only when the volume key events aren't handled by foreground
+     *          activity. {@code false} otherwise to tell session about the real caller.
+     * @param direction The direction to adjust volume in.
+     * @param flags Any of the flags from {@link AudioManager}.
+     * @param useSuggested True to use adjustSuggestedStreamVolume instead of
+     */
+    void adjustVolume(String packageName, String opPackageName, int pid, int uid,
+            boolean asSystemService, int direction, int flags, boolean useSuggested);
+
+    /**
+     * Check if this session has been set to active by the app. (i.e. ready to receive command and
+     * getters are available).
+     *
+     * @return True if the session is active, false otherwise.
+     */
+    // TODO(jaewan): Find better naming, or remove this from the MediaSessionRecordImpl.
+    boolean isActive();
+
+    /**
+     * Check if the session's playback active state matches with the expectation. This always return
+     * {@code false} if the playback state is unknown (e.g. {@code null}), where we cannot know the
+     * actual playback state associated with the session.
+     *
+     * @param expected True if playback is expected to be active. false otherwise.
+     * @return True if the session's playback matches with the expectation. false otherwise.
+     */
+    boolean checkPlaybackActiveState(boolean expected);
+
+    /**
+     * Check whether the playback type is local or remote.
+     * <p>
+     * <ul>
+     *   <li>Local: volume changes the stream volume because playback happens on this device.</li>
+     *   <li>Remote: volume is sent to the apps callback because playback happens on the remote
+     *     device and we cannot know how to control the volume of it.</li>
+     * </ul>
+     *
+     * @return {@code true} if the playback is local. {@code false} if the playback is remote.
+     */
+    boolean isPlaybackTypeLocal();
+
+    /**
+     * Sends media button.
+     *
+     * @param packageName caller package name
+     * @param pid caller pid
+     * @param uid caller uid
+     * @param asSystemService {@code true} if the event sent to the session as if it was come from
+     *          the system service instead of the app process.
+     * @param ke key events
+     * @param sequenceId (optional) sequence id. Use this only when a wake lock is needed.
+     * @param cb (optional) result receiver to receive callback. Use this only when a wake lock is
+     *           needed.
+     * @return {@code true} if the attempt to send media button was successfully.
+     *         {@code false} otherwise.
+     */
+    boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
+            KeyEvent ke, int sequenceId, ResultReceiver cb);
+
+    /**
+     * Dumps internal state
+     *
+     * @param pw print writer
+     * @param prefix prefix
+     */
+    void dump(PrintWriter pw, String prefix);
+
+    /**
+     * Override {@link AutoCloseable#close} to tell not to throw exception.
+     */
+    @Override
+    void close();
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 0f059db..f71fb58 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -40,10 +40,7 @@
 import android.media.AudioManagerInternal;
 import android.media.AudioPlaybackConfiguration;
 import android.media.AudioSystem;
-import android.media.IAudioService;
 import android.media.IRemoteVolumeController;
-import android.media.MediaController2;
-import android.media.Session2CommandGroup;
 import android.media.Session2Token;
 import android.media.session.IActiveSessionsListener;
 import android.media.session.IOnMediaKeyEventDispatchedListener;
@@ -61,7 +58,6 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.PowerManager;
@@ -123,11 +119,6 @@
     @GuardedBy("mLock")
     private final ArrayList<SessionsListenerRecord> mSessionsListeners =
             new ArrayList<SessionsListenerRecord>();
-    // Map user id as index to list of Session2Tokens
-    // TODO: Keep session2 info in MediaSessionStack for prioritizing both session1 and session2 in
-    //       one place.
-    @GuardedBy("mLock")
-    private final SparseArray<List<Session2Token>> mSession2TokensPerUser = new SparseArray<>();
     @GuardedBy("mLock")
     private final List<Session2TokensListenerRecord> mSession2TokensListenerRecords =
             new ArrayList<>();
@@ -189,16 +180,11 @@
         updateUser();
     }
 
-    private IAudioService getAudioService() {
-        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
-        return IAudioService.Stub.asInterface(b);
-    }
-
     private boolean isGlobalPriorityActiveLocked() {
         return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
     }
 
-    void updateSession(MediaSessionRecord record) {
+    void onSessionActiveStateChanged(MediaSessionRecordImpl record) {
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(record.getUserId());
             if (user == null) {
@@ -215,12 +201,14 @@
                     Log.w(TAG, "Unknown session updated. Ignoring.");
                     return;
                 }
-                user.mPriorityStack.onSessionStateChange(record);
+                user.mPriorityStack.onSessionActiveStateChanged(record);
             }
-            mHandler.postSessionsChanged(record.getUserId());
+
+            mHandler.postSessionsChanged(record);
         }
     }
 
+    // Currently only media1 can become global priority session.
     void setGlobalPrioritySession(MediaSessionRecord record) {
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(record.getUserId());
@@ -266,11 +254,13 @@
     List<Session2Token> getSession2TokensLocked(int userId) {
         List<Session2Token> list = new ArrayList<>();
         if (userId == USER_ALL) {
-            for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
-                list.addAll(mSession2TokensPerUser.valueAt(i));
+            int size = mUserRecords.size();
+            for (int i = 0; i < size; i++) {
+                list.addAll(mUserRecords.valueAt(i).mPriorityStack.getSession2Tokens(userId));
             }
         } else {
-            list.addAll(mSession2TokensPerUser.get(userId));
+            FullUserRecord user = getFullUserRecordLocked(userId);
+            list.addAll(user.mPriorityStack.getSession2Tokens(userId));
         }
         return list;
     }
@@ -297,14 +287,15 @@
         }
     }
 
-    void onSessionPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
+    void onSessionPlaybackStateChanged(MediaSessionRecordImpl record,
+            boolean shouldUpdatePriority) {
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(record.getUserId());
             if (user == null || !user.mPriorityStack.contains(record)) {
                 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
                 return;
             }
-            user.mPriorityStack.onPlaystateChanged(record, oldState, newState);
+            user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
         }
     }
 
@@ -347,7 +338,6 @@
                     user.destroySessionsForUserLocked(userId);
                 }
             }
-            mSession2TokensPerUser.remove(userId);
             updateUser();
         }
     }
@@ -366,13 +356,7 @@
         }
     }
 
-    void sessionDied(MediaSessionRecord session) {
-        synchronized (mLock) {
-            destroySessionLocked(session);
-        }
-    }
-
-    void destroySession(MediaSessionRecord session) {
+    void onSessionDied(MediaSessionRecordImpl session) {
         synchronized (mLock) {
             destroySessionLocked(session);
         }
@@ -393,9 +377,6 @@
                             mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id));
                         }
                     }
-                    if (mSession2TokensPerUser.get(userInfo.id) == null) {
-                        mSession2TokensPerUser.put(userInfo.id, new ArrayList<>());
-                    }
                 }
             }
             // Ensure that the current full user exists.
@@ -405,9 +386,6 @@
                 Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
                 mCurrentFullUserRecord = new FullUserRecord(currentFullUserId);
                 mUserRecords.put(currentFullUserId, mCurrentFullUserRecord);
-                if (mSession2TokensPerUser.get(currentFullUserId) == null) {
-                    mSession2TokensPerUser.put(currentFullUserId, new ArrayList<>());
-                }
             }
             mFullUserIds.put(currentFullUserId, currentFullUserId);
         }
@@ -444,7 +422,7 @@
      * 5. We need to unlink to death from the cb binder
      * 6. We need to tell the session to do any final cleanup (onDestroy)
      */
-    private void destroySessionLocked(MediaSessionRecord session) {
+    private void destroySessionLocked(MediaSessionRecordImpl session) {
         if (DEBUG) {
             Log.d(TAG, "Destroying " + session);
         }
@@ -461,7 +439,7 @@
         }
 
         session.close();
-        mHandler.postSessionsChanged(session.getUserId());
+        mHandler.postSessionsChanged(session);
     }
 
     private void enforcePackageName(String packageName, int uid) {
@@ -541,15 +519,6 @@
         return false;
     }
 
-    private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
-            String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo)
-            throws RemoteException {
-        synchronized (mLock) {
-            return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb,
-                    tag, sessionInfo);
-        }
-    }
-
     /*
      * When a session is created the following things need to happen.
      * 1. Its callback binder needs a link to death
@@ -557,29 +526,31 @@
      * 3. It needs to be added to the priority stack.
      * 4. It needs to be added to the relevant user record.
      */
-    private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
+    private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
             String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo) {
-        FullUserRecord user = getFullUserRecordLocked(userId);
-        if (user == null) {
-            Log.w(TAG, "Request from invalid user: " +  userId + ", pkg=" + callerPackageName);
-            throw new RuntimeException("Session request from invalid user.");
-        }
+        synchronized (mLock) {
+            FullUserRecord user = getFullUserRecordLocked(userId);
+            if (user == null) {
+                Log.w(TAG, "Request from invalid user: " +  userId + ", pkg=" + callerPackageName);
+                throw new RuntimeException("Session request from invalid user.");
+            }
 
-        final MediaSessionRecord session;
-        try {
-            session = new MediaSessionRecord(callerPid, callerUid, userId,
-                    callerPackageName, cb, tag, sessionInfo, this, mHandler.getLooper());
-        } catch (RemoteException e) {
-            throw new RuntimeException("Media Session owner died prematurely.", e);
-        }
+            final MediaSessionRecord session;
+            try {
+                session = new MediaSessionRecord(callerPid, callerUid, userId,
+                        callerPackageName, cb, tag, sessionInfo, this, mHandler.getLooper());
+            } catch (RemoteException e) {
+                throw new RuntimeException("Media Session owner died prematurely.", e);
+            }
 
-        user.mPriorityStack.addSession(session);
-        mHandler.postSessionsChanged(userId);
+            user.mPriorityStack.addSession(session);
+            mHandler.postSessionsChanged(session);
 
-        if (DEBUG) {
-            Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
+            if (DEBUG) {
+                Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
+            }
+            return session;
         }
-        return session;
     }
 
     private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
@@ -600,16 +571,16 @@
         return -1;
     }
 
-    private void pushSessionsChanged(int userId) {
+    private void pushSession1Changed(int userId) {
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(userId);
             if (user == null) {
-                Log.w(TAG, "pushSessionsChanged failed. No user with id=" + userId);
+                Log.w(TAG, "pushSession1ChangedOnHandler failed. No user with id=" + userId);
                 return;
             }
             List<MediaSessionRecord> records = getActiveSessionsLocked(userId);
             int size = records.size();
-            ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
+            ArrayList<MediaSession.Token> tokens = new ArrayList<>();
             for (int i = 0; i < size; i++) {
                 tokens.add(records.get(i).getSessionToken());
             }
@@ -629,6 +600,27 @@
         }
     }
 
+    void pushSession2Changed(int userId) {
+        synchronized (mLock) {
+            List<Session2Token> allSession2Tokens = getSession2TokensLocked(USER_ALL);
+            List<Session2Token> session2Tokens = getSession2TokensLocked(userId);
+
+            for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
+                Session2TokensListenerRecord listenerRecord = mSession2TokensListenerRecords.get(i);
+                try {
+                    if (listenerRecord.userId == USER_ALL) {
+                        listenerRecord.listener.onSession2TokensChanged(allSession2Tokens);
+                    } else if (listenerRecord.userId == userId) {
+                        listenerRecord.listener.onSession2TokensChanged(session2Tokens);
+                    }
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to notify Session2Token change. Removing listener.", e);
+                    mSession2TokensListenerRecords.remove(i);
+                }
+            }
+        }
+    }
+
     private void pushRemoteVolumeUpdateLocked(int userId) {
         FullUserRecord user = getFullUserRecordLocked(userId);
         if (user == null) {
@@ -638,8 +630,13 @@
 
         synchronized (mLock) {
             int size = mRemoteVolumeControllers.beginBroadcast();
-            MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
-            MediaSession.Token token = record == null ? null : record.getSessionToken();
+            MediaSessionRecordImpl record = user.mPriorityStack.getDefaultRemoteSession(userId);
+            if (record instanceof MediaSession2Record) {
+                // TODO(jaewan): Implement
+                return;
+            }
+            MediaSession.Token token = record == null
+                    ? null : ((MediaSessionRecord) record).getSessionToken();
 
             for (int i = size - 1; i >= 0; i--) {
                 try {
@@ -653,34 +650,15 @@
         }
     }
 
-    void pushSession2TokensChangedLocked(int userId) {
-        List<Session2Token> allSession2Tokens = getSession2TokensLocked(USER_ALL);
-        List<Session2Token> session2Tokens = getSession2TokensLocked(userId);
-
-        for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
-            Session2TokensListenerRecord listenerRecord = mSession2TokensListenerRecords.get(i);
-            try {
-                if (listenerRecord.userId == USER_ALL) {
-                    listenerRecord.listener.onSession2TokensChanged(allSession2Tokens);
-                } else if (listenerRecord.userId == userId) {
-                    listenerRecord.listener.onSession2TokensChanged(session2Tokens);
-                }
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to notify Session2Token change. Removing listener.", e);
-                mSession2TokensListenerRecords.remove(i);
-            }
-        }
-    }
-
     /**
      * Called when the media button receiver for the {@code record} is changed.
      *
      * @param record the media session whose media button receiver is updated.
      */
-    public void onMediaButtonReceiverChanged(MediaSessionRecord record) {
+    public void onMediaButtonReceiverChanged(MediaSessionRecordImpl record) {
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(record.getUserId());
-            MediaSessionRecord mediaButtonSession =
+            MediaSessionRecordImpl mediaButtonSession =
                     user.mPriorityStack.getMediaButtonSession();
             if (record == mediaButtonSession) {
                 user.rememberMediaButtonReceiverLocked(mediaButtonSession);
@@ -868,39 +846,34 @@
             pw.println(indent + "Restored MediaButtonReceiverComponentType: "
                     + mRestoredMediaButtonReceiverComponentType);
             mPriorityStack.dump(pw, indent);
-            pw.println(indent + "Session2Tokens:");
-            for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
-                List<Session2Token> list = mSession2TokensPerUser.valueAt(i);
-                if (list == null || list.size() == 0) {
-                    continue;
-                }
-                for (Session2Token token : list) {
-                    pw.println(indent + "  " + token);
-                }
-            }
         }
 
         @Override
-        public void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
-                MediaSessionRecord newMediaButtonSession) {
+        public void onMediaButtonSessionChanged(MediaSessionRecordImpl oldMediaButtonSession,
+                MediaSessionRecordImpl newMediaButtonSession) {
             if (DEBUG_KEY_EVENT) {
                 Log.d(TAG, "Media button session is changed to " + newMediaButtonSession);
             }
             synchronized (mLock) {
                 if (oldMediaButtonSession != null) {
-                    mHandler.postSessionsChanged(oldMediaButtonSession.getUserId());
+                    mHandler.postSessionsChanged(oldMediaButtonSession);
                 }
                 if (newMediaButtonSession != null) {
                     rememberMediaButtonReceiverLocked(newMediaButtonSession);
-                    mHandler.postSessionsChanged(newMediaButtonSession.getUserId());
+                    mHandler.postSessionsChanged(newMediaButtonSession);
                 }
                 pushAddressedPlayerChangedLocked();
             }
         }
 
         // Remember media button receiver and keep it in the persistent storage.
-        public void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
-            PendingIntent receiver = record.getMediaButtonReceiver();
+        public void rememberMediaButtonReceiverLocked(MediaSessionRecordImpl record) {
+            if (record instanceof MediaSession2Record) {
+                // TODO(jaewan): Implement
+                return;
+            }
+            MediaSessionRecord sessionRecord = (MediaSessionRecord) record;
+            PendingIntent receiver = sessionRecord.getMediaButtonReceiver();
             mLastMediaButtonReceiver = receiver;
             mRestoredMediaButtonReceiver = null;
             mRestoredMediaButtonReceiverComponentType = COMPONENT_TYPE_INVALID;
@@ -925,10 +898,15 @@
         private void pushAddressedPlayerChangedLocked(
                 IOnMediaKeyEventSessionChangedListener callback) {
             try {
-                MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
+                MediaSessionRecordImpl mediaButtonSession = getMediaButtonSessionLocked();
                 if (mediaButtonSession != null) {
-                    callback.onMediaKeyEventSessionChanged(mediaButtonSession.getPackageName(),
-                            mediaButtonSession.getSessionToken());
+                    if (mediaButtonSession instanceof MediaSessionRecord) {
+                        MediaSessionRecord session1 = (MediaSessionRecord) mediaButtonSession;
+                        callback.onMediaKeyEventSessionChanged(session1.getPackageName(),
+                                session1.getSessionToken());
+                    } else {
+                        // TODO(jaewan): Implement
+                    }
                 } else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
                     callback.onMediaKeyEventSessionChanged(
                             mCurrentFullUserRecord.mLastMediaButtonReceiver
@@ -951,7 +929,7 @@
             }
         }
 
-        private MediaSessionRecord getMediaButtonSessionLocked() {
+        private MediaSessionRecordImpl getMediaButtonSessionLocked() {
             return isGlobalPriorityActiveLocked()
                     ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
         }
@@ -1132,14 +1110,13 @@
                     throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
                             + " but actually=" + sessionToken.getUid());
                 }
-                Controller2Callback callback = new Controller2Callback(sessionToken);
-                // Note: It's safe not to keep controller here because it wouldn't be GC'ed until
-                //       it's closed.
-                // TODO: Keep controller as well for better readability
-                //       because the GC behavior isn't straightforward.
-                MediaController2 controller = new MediaController2.Builder(mContext, sessionToken)
-                        .setControllerCallback(new HandlerExecutor(mHandler), callback)
-                        .build();
+                MediaSession2Record record = new MediaSession2Record(
+                        sessionToken, MediaSessionService.this, mHandler.getLooper());
+                synchronized (mLock) {
+                    FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+                    user.mPriorityStack.addSession(record);
+                }
+                // Do not immediately notify changes -- do so when framework can dispatch command
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1180,7 +1157,8 @@
                         null /* optional packageName */);
                 List<Session2Token> result;
                 synchronized (mLock) {
-                    result = getSession2TokensLocked(resolvedUserId);
+                    FullUserRecord user = getFullUserRecordLocked(userId);
+                    result = user.mPriorityStack.getSession2Tokens(resolvedUserId);
                 }
                 return new ParceledListSlice(result);
             } finally {
@@ -2018,7 +1996,7 @@
 
         private void dispatchAdjustVolumeLocked(String packageName, String opPackageName, int pid,
                 int uid, boolean asSystemService, int suggestedStream, int direction, int flags) {
-            MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
+            MediaSessionRecordImpl session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
                     : mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();
 
             boolean preferSuggestedStream = false;
@@ -2109,7 +2087,13 @@
 
         private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid,
                 boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
-            MediaSessionRecord session = mCurrentFullUserRecord.getMediaButtonSessionLocked();
+            if (mCurrentFullUserRecord.getMediaButtonSessionLocked()
+                    instanceof MediaSession2Record) {
+                // TODO(jaewan): Implement
+                return;
+            }
+            MediaSessionRecord session =
+                    (MediaSessionRecord) mCurrentFullUserRecord.getMediaButtonSessionLocked();
             if (session != null) {
                 if (DEBUG_KEY_EVENT) {
                     Log.d(TAG, "Sending " + keyEvent + " to " + session);
@@ -2389,15 +2373,19 @@
     }
 
     final class MessageHandler extends Handler {
-        private static final int MSG_SESSIONS_CHANGED = 1;
-        private static final int MSG_VOLUME_INITIAL_DOWN = 2;
+        private static final int MSG_SESSIONS_1_CHANGED = 1;
+        private static final int MSG_SESSIONS_2_CHANGED = 2;
+        private static final int MSG_VOLUME_INITIAL_DOWN = 3;
         private final SparseArray<Integer> mIntegerCache = new SparseArray<>();
 
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_SESSIONS_CHANGED:
-                    pushSessionsChanged((int) msg.obj);
+                case MSG_SESSIONS_1_CHANGED:
+                    pushSession1Changed((int) msg.obj);
+                    break;
+                case MSG_SESSIONS_2_CHANGED:
+                    pushSession2Changed((int) msg.obj);
                     break;
                 case MSG_VOLUME_INITIAL_DOWN:
                     synchronized (mLock) {
@@ -2412,41 +2400,19 @@
             }
         }
 
-        public void postSessionsChanged(int userId) {
+        public void postSessionsChanged(MediaSessionRecordImpl record) {
             // Use object instead of the arguments when posting message to remove pending requests.
-            Integer userIdInteger = mIntegerCache.get(userId);
+            Integer userIdInteger = mIntegerCache.get(record.getUserId());
             if (userIdInteger == null) {
-                userIdInteger = Integer.valueOf(userId);
-                mIntegerCache.put(userId, userIdInteger);
+                userIdInteger = Integer.valueOf(record.getUserId());
+                mIntegerCache.put(record.getUserId(), userIdInteger);
             }
-            removeMessages(MSG_SESSIONS_CHANGED, userIdInteger);
-            obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
+
+            int msg = (record instanceof MediaSessionRecord)
+                    ? MSG_SESSIONS_1_CHANGED : MSG_SESSIONS_2_CHANGED;
+            removeMessages(msg, userIdInteger);
+            obtainMessage(msg, userIdInteger).sendToTarget();
         }
     }
 
-    private class Controller2Callback extends MediaController2.ControllerCallback {
-        private final Session2Token mToken;
-
-        Controller2Callback(Session2Token token) {
-            mToken = token;
-        }
-
-        @Override
-        public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
-            synchronized (mLock) {
-                int userId = UserHandle.getUserId(mToken.getUid());
-                mSession2TokensPerUser.get(userId).add(mToken);
-                pushSession2TokensChangedLocked(userId);
-            }
-        }
-
-        @Override
-        public void onDisconnected(MediaController2 controller) {
-            synchronized (mLock) {
-                int userId = UserHandle.getUserId(mToken.getUid());
-                mSession2TokensPerUser.get(userId).remove(mToken);
-                pushSession2TokensChangedLocked(userId);
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 732563f..7bb7cf4 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -16,8 +16,8 @@
 
 package com.android.server.media;
 
+import android.media.Session2Token;
 import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
 import android.os.Debug;
 import android.os.UserHandle;
 import android.util.IntArray;
@@ -45,51 +45,30 @@
         /**
          * Called when the media button session is changed.
          */
-        void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
-                MediaSessionRecord newMediaButtonSession);
+        void onMediaButtonSessionChanged(MediaSessionRecordImpl oldMediaButtonSession,
+                MediaSessionRecordImpl newMediaButtonSession);
     }
 
     /**
-     * These are states that usually indicate the user took an action and should
-     * bump priority regardless of the old state.
+     * Sorted list of the media sessions
      */
-    private static final int[] ALWAYS_PRIORITY_STATES = {
-            PlaybackState.STATE_FAST_FORWARDING,
-            PlaybackState.STATE_REWINDING,
-            PlaybackState.STATE_SKIPPING_TO_PREVIOUS,
-            PlaybackState.STATE_SKIPPING_TO_NEXT };
-    /**
-     * These are states that usually indicate the user took an action if they
-     * were entered from a non-priority state.
-     */
-    private static final int[] TRANSITION_PRIORITY_STATES = {
-            PlaybackState.STATE_BUFFERING,
-            PlaybackState.STATE_CONNECTING,
-            PlaybackState.STATE_PLAYING };
-
-    /**
-     * Sorted list of the media sessions.
-     * The session of which PlaybackState is changed to ALWAYS_PRIORITY_STATES or
-     * TRANSITION_PRIORITY_STATES comes first.
-     * @see #shouldUpdatePriority
-     */
-    private final List<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
+    private final List<MediaSessionRecordImpl> mSessions = new ArrayList<>();
 
     private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
     private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener;
 
     /**
      * The media button session which receives media key events.
-     * It could be null if the previous media buttion session is released.
+     * It could be null if the previous media button session is released.
      */
-    private MediaSessionRecord mMediaButtonSession;
+    private MediaSessionRecordImpl mMediaButtonSession;
 
-    private MediaSessionRecord mCachedVolumeDefault;
+    private MediaSessionRecordImpl mCachedVolumeDefault;
 
     /**
      * Cache the result of the {@link #getActiveSessions} per user.
      */
-    private final SparseArray<ArrayList<MediaSessionRecord>> mCachedActiveLists =
+    private final SparseArray<List<MediaSessionRecord>> mCachedActiveLists =
             new SparseArray<>();
 
     MediaSessionStack(AudioPlayerStateMonitor monitor, OnMediaButtonSessionChangedListener listener) {
@@ -102,7 +81,7 @@
      *
      * @param record The record to add.
      */
-    public void addSession(MediaSessionRecord record) {
+    public void addSession(MediaSessionRecordImpl record) {
         mSessions.add(record);
         clearCache(record.getUserId());
 
@@ -117,7 +96,7 @@
      *
      * @param record The record to remove.
      */
-    public void removeSession(MediaSessionRecord record) {
+    public void removeSession(MediaSessionRecordImpl record) {
         mSessions.remove(record);
         if (mMediaButtonSession == record) {
             // When the media button session is removed, nullify the media button session and do not
@@ -131,7 +110,7 @@
     /**
      * Return if the record exists in the priority tracker.
      */
-    public boolean contains(MediaSessionRecord record) {
+    public boolean contains(MediaSessionRecordImpl record) {
         return mSessions.contains(record);
     }
 
@@ -142,9 +121,12 @@
      * @return the MediaSessionRecord. Can be {@code null} if the session is gone meanwhile.
      */
     public MediaSessionRecord getMediaSessionRecord(MediaSession.Token sessionToken) {
-        for (MediaSessionRecord record : mSessions) {
-            if (Objects.equals(record.getSessionToken(), sessionToken)) {
-                return record;
+        for (MediaSessionRecordImpl record : mSessions) {
+            if (record instanceof MediaSessionRecord) {
+                MediaSessionRecord session1 = (MediaSessionRecord) record;
+                if (Objects.equals(session1.getSessionToken(), sessionToken)) {
+                    return session1;
+                }
             }
         }
         return null;
@@ -154,15 +136,15 @@
      * Notify the priority tracker that a session's playback state changed.
      *
      * @param record The record that changed.
-     * @param oldState Its old playback state.
-     * @param newState Its new playback state.
+     * @param shouldUpdatePriority {@code true} if the record needs to prioritized
      */
-    public void onPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
-        if (shouldUpdatePriority(oldState, newState)) {
+    public void onPlaybackStateChanged(
+            MediaSessionRecordImpl record, boolean shouldUpdatePriority) {
+        if (shouldUpdatePriority) {
             mSessions.remove(record);
             mSessions.add(0, record);
             clearCache(record.getUserId());
-        } else if (!MediaSession.isActiveState(newState)) {
+        } else if (record.checkPlaybackActiveState(false)) {
             // Just clear the volume cache when a state goes inactive
             mCachedVolumeDefault = null;
         }
@@ -172,7 +154,7 @@
         // In that case, we pick the media session whose PlaybackState matches
         // the audio playback configuration.
         if (mMediaButtonSession != null && mMediaButtonSession.getUid() == record.getUid()) {
-            MediaSessionRecord newMediaButtonSession =
+            MediaSessionRecordImpl newMediaButtonSession =
                     findMediaButtonSession(mMediaButtonSession.getUid());
             if (newMediaButtonSession != mMediaButtonSession) {
                 updateMediaButtonSession(newMediaButtonSession);
@@ -185,7 +167,7 @@
      *
      * @param record The record that changed.
      */
-    public void onSessionStateChange(MediaSessionRecord record) {
+    public void onSessionActiveStateChanged(MediaSessionRecordImpl record) {
         // For now just clear the cache. Eventually we'll selectively clear
         // depending on what changed.
         clearCache(record.getUserId());
@@ -203,7 +185,7 @@
         }
         IntArray audioPlaybackUids = mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
         for (int i = 0; i < audioPlaybackUids.size(); i++) {
-            MediaSessionRecord mediaButtonSession =
+            MediaSessionRecordImpl mediaButtonSession =
                     findMediaButtonSession(audioPlaybackUids.get(i));
             if (mediaButtonSession != null) {
                 // Found the media button session.
@@ -225,9 +207,9 @@
      * @return The media button session. Returns {@code null} if the app doesn't have a media
      *   session.
      */
-    private MediaSessionRecord findMediaButtonSession(int uid) {
-        MediaSessionRecord mediaButtonSession = null;
-        for (MediaSessionRecord session : mSessions) {
+    private MediaSessionRecordImpl findMediaButtonSession(int uid) {
+        MediaSessionRecordImpl mediaButtonSession = null;
+        for (MediaSessionRecordImpl session : mSessions) {
             if (uid == session.getUid()) {
                 if (session.checkPlaybackActiveState(
                         mAudioPlayerStateMonitor.isPlaybackActive(session.getUid()))) {
@@ -253,8 +235,8 @@
      *    for all users in this {@link MediaSessionStack}.
      * @return All the active sessions in priority order.
      */
-    public ArrayList<MediaSessionRecord> getActiveSessions(int userId) {
-        ArrayList<MediaSessionRecord> cachedActiveList = mCachedActiveLists.get(userId);
+    public List<MediaSessionRecord> getActiveSessions(int userId) {
+        List<MediaSessionRecord> cachedActiveList = mCachedActiveLists.get(userId);
         if (cachedActiveList == null) {
             cachedActiveList = getPriorityList(true, userId);
             mCachedActiveLists.put(userId, cachedActiveList);
@@ -263,26 +245,46 @@
     }
 
     /**
+     * Gets the session2 tokens.
+     *
+     * @param userId The user to check. It can be {@link UserHandle#USER_ALL} to get all session2
+     *    tokens for all users in this {@link MediaSessionStack}.
+     * @return All session2 tokens.
+     */
+    public List<Session2Token> getSession2Tokens(int userId) {
+        ArrayList<Session2Token> session2Records = new ArrayList<>();
+        for (MediaSessionRecordImpl record : mSessions) {
+            if ((userId == UserHandle.USER_ALL || record.getUserId() == userId)
+                    && record.isActive()
+                    && record instanceof MediaSession2Record) {
+                MediaSession2Record session2 = (MediaSession2Record) record;
+                session2Records.add(session2.getSession2Token());
+            }
+        }
+        return session2Records;
+    }
+
+    /**
      * Get the media button session which receives the media button events.
      *
      * @return The media button session or null.
      */
-    public MediaSessionRecord getMediaButtonSession() {
+    public MediaSessionRecordImpl getMediaButtonSession() {
         return mMediaButtonSession;
     }
 
-    private void updateMediaButtonSession(MediaSessionRecord newMediaButtonSession) {
-        MediaSessionRecord oldMediaButtonSession = mMediaButtonSession;
+    private void updateMediaButtonSession(MediaSessionRecordImpl newMediaButtonSession) {
+        MediaSessionRecordImpl oldMediaButtonSession = mMediaButtonSession;
         mMediaButtonSession = newMediaButtonSession;
         mOnMediaButtonSessionChangedListener.onMediaButtonSessionChanged(
                 oldMediaButtonSession, newMediaButtonSession);
     }
 
-    public MediaSessionRecord getDefaultVolumeSession() {
+    public MediaSessionRecordImpl getDefaultVolumeSession() {
         if (mCachedVolumeDefault != null) {
             return mCachedVolumeDefault;
         }
-        ArrayList<MediaSessionRecord> records = getPriorityList(true, UserHandle.USER_ALL);
+        List<MediaSessionRecord> records = getPriorityList(true, UserHandle.USER_ALL);
         int size = records.size();
         for (int i = 0; i < size; i++) {
             MediaSessionRecord record = records.get(i);
@@ -294,13 +296,13 @@
         return null;
     }
 
-    public MediaSessionRecord getDefaultRemoteSession(int userId) {
-        ArrayList<MediaSessionRecord> records = getPriorityList(true, userId);
+    public MediaSessionRecordImpl getDefaultRemoteSession(int userId) {
+        List<MediaSessionRecord> records = getPriorityList(true, userId);
 
         int size = records.size();
         for (int i = 0; i < size; i++) {
             MediaSessionRecord record = records.get(i);
-            if (!record.isPlaybackLocal()) {
+            if (!record.isPlaybackTypeLocal()) {
                 return record;
             }
         }
@@ -308,16 +310,11 @@
     }
 
     public void dump(PrintWriter pw, String prefix) {
-        ArrayList<MediaSessionRecord> sortedSessions = getPriorityList(false,
-                UserHandle.USER_ALL);
-        int count = sortedSessions.size();
         pw.println(prefix + "Media button session is " + mMediaButtonSession);
-        pw.println(prefix + "Sessions Stack - have " + count + " sessions:");
+        pw.println(prefix + "Sessions Stack - have " + mSessions.size() + " sessions:");
         String indent = prefix + "  ";
-        for (int i = 0; i < count; i++) {
-            MediaSessionRecord record = sortedSessions.get(i);
+        for (MediaSessionRecordImpl record : mSessions) {
             record.dump(pw, indent);
-            pw.println();
         }
     }
 
@@ -335,17 +332,19 @@
      *            will return sessions for all users.
      * @return The priority sorted list of sessions.
      */
-    public ArrayList<MediaSessionRecord> getPriorityList(boolean activeOnly, int userId) {
-        ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
+    public List<MediaSessionRecord> getPriorityList(boolean activeOnly, int userId) {
+        List<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
         int lastPlaybackActiveIndex = 0;
         int lastActiveIndex = 0;
 
-        int size = mSessions.size();
-        for (int i = 0; i < size; i++) {
-            final MediaSessionRecord session = mSessions.get(i);
+        for (MediaSessionRecordImpl record : mSessions) {
+            if (!(record instanceof MediaSessionRecord)) {
+                continue;
+            }
+            final MediaSessionRecord session = (MediaSessionRecord) record;
 
-            if (userId != UserHandle.USER_ALL && userId != session.getUserId()) {
-                // Filter out sessions for the wrong user
+            if ((userId != UserHandle.USER_ALL && userId != session.getUserId())) {
+                // Filter out sessions for the wrong user or session2.
                 continue;
             }
 
@@ -369,26 +368,6 @@
         return result;
     }
 
-    private boolean shouldUpdatePriority(int oldState, int newState) {
-        if (containsState(newState, ALWAYS_PRIORITY_STATES)) {
-            return true;
-        }
-        if (!containsState(oldState, TRANSITION_PRIORITY_STATES)
-                && containsState(newState, TRANSITION_PRIORITY_STATES)) {
-            return true;
-        }
-        return false;
-    }
-
-    private boolean containsState(int state, int[] states) {
-        for (int i = 0; i < states.length; i++) {
-            if (states[i] == state) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     private void clearCache(int userId) {
         mCachedVolumeDefault = null;
         mCachedActiveLists.remove(userId);
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 8fdfcbf..961d3d0 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -30,12 +30,12 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.R;
 
 import java.util.Collections;
+import java.util.List;
 
 /**
  * Provides routes for local playbacks such as phone speaker, wired headset, or Bluetooth speakers.
@@ -48,13 +48,14 @@
     static final String BLUETOOTH_ROUTE_ID = "BLUETOOTH_ROUTE";
 
     // TODO: Move these to a proper place
-    public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
-    public static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
+    public static final String TYPE_LIVE_AUDIO = "android.media.intent.route.TYPE_LIVE_AUDIO";
+    public static final String TYPE_LIVE_VIDEO = "android.media.intent.route.TYPE_LIVE_VIDEO";
 
     private final AudioManager mAudioManager;
     private final IAudioService mAudioService;
     private final Handler mHandler;
     private final Context mContext;
+    private final BluetoothRouteProvider mBtRouteProvider;
 
     private static ComponentName sComponentName = new ComponentName(
             SystemMediaRoute2Provider.class.getPackageName$(),
@@ -62,7 +63,7 @@
 
     //TODO: Clean up these when audio manager support multiple bt devices
     MediaRoute2Info mDefaultRoute;
-    MediaRoute2Info mBluetoothA2dpRoute;
+    @NonNull List<MediaRoute2Info> mBluetoothRoutes = Collections.EMPTY_LIST;
     final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
 
     final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
@@ -87,48 +88,52 @@
         mAudioService = IAudioService.Stub.asInterface(
                 ServiceManager.getService(Context.AUDIO_SERVICE));
 
+        mBtRouteProvider = BluetoothRouteProvider.getInstance(context, (routes) -> {
+            mBluetoothRoutes = routes;
+            publishRoutes();
+        });
         initializeRoutes();
     }
 
     @Override
-    public void requestCreateSession(String packageName, String routeId, String controlCategory,
+    public void requestCreateSession(String packageName, String routeId, String routeType,
             long requestId) {
         // Do nothing
     }
 
     @Override
-    public void releaseSession(int sessionId) {
+    public void releaseSession(String sessionId) {
         // Do nothing
     }
 
     @Override
-    public void selectRoute(int sessionId, MediaRoute2Info route) {
+    public void selectRoute(String sessionId, String routeId) {
         //TODO: implement method
     }
 
     @Override
-    public void deselectRoute(int sessionId, MediaRoute2Info route) {
+    public void deselectRoute(String sessionId, String routeId) {
         //TODO: implement method
     }
 
     @Override
-    public void transferToRoute(int sessionId, MediaRoute2Info route) {
+    public void transferToRoute(String sessionId, String routeId) {
         //TODO: implement method
     }
 
     //TODO: implement method
     @Override
-    public void sendControlRequest(@NonNull MediaRoute2Info route, @NonNull Intent request) {
+    public void sendControlRequest(@NonNull String routeId, @NonNull Intent request) {
     }
 
     //TODO: implement method
     @Override
-    public void requestSetVolume(MediaRoute2Info route, int volume) {
+    public void requestSetVolume(String routeId, int volume) {
     }
 
     //TODO: implement method
     @Override
-    public void requestUpdateVolume(MediaRoute2Info route, int delta) {
+    public void requestUpdateVolume(String routeId, int delta) {
     }
 
     void initializeRoutes() {
@@ -141,8 +146,8 @@
                         : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
                 .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
                 .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
-                .addSupportedCategory(CATEGORY_LIVE_AUDIO)
-                .addSupportedCategory(CATEGORY_LIVE_VIDEO)
+                .addFeature(TYPE_LIVE_AUDIO)
+                .addFeature(TYPE_LIVE_VIDEO)
                 .build();
 
         AudioRoutesInfo newAudioRoutes = null;
@@ -157,7 +162,15 @@
             updateAudioRoutes(newAudioRoutes);
         }
 
-        publishRoutes();
+        mBluetoothRoutes = mBtRouteProvider.getBluetoothRoutes();
+
+        MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
+        builder.addRoute(mDefaultRoute);
+        for (MediaRoute2Info route : mBluetoothRoutes) {
+            builder.addRoute(route);
+        }
+        setProviderState(builder.build(), Collections.emptyList());
+        mHandler.post(() -> notifyProviderState());
     }
 
     void updateAudioRoutes(AudioRoutesInfo newRoutes) {
@@ -181,25 +194,10 @@
                         : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
                 .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
                 .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
-                .addSupportedCategory(CATEGORY_LIVE_AUDIO)
-                .addSupportedCategory(CATEGORY_LIVE_VIDEO)
+                .addFeature(TYPE_LIVE_AUDIO)
+                .addFeature(TYPE_LIVE_VIDEO)
                 .build();
 
-        if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
-            mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
-            if (mCurAudioRoutesInfo.bluetoothName != null) {
-                //TODO: mark as bluetooth once MediaRoute2Info has device type
-                mBluetoothA2dpRoute = new MediaRoute2Info.Builder(BLUETOOTH_ROUTE_ID,
-                        mCurAudioRoutesInfo.bluetoothName)
-                        .setDescription(mContext.getResources().getText(
-                                R.string.bluetooth_a2dp_audio_route_name).toString())
-                        .addSupportedCategory(CATEGORY_LIVE_AUDIO)
-                        .build();
-            } else {
-                mBluetoothA2dpRoute = null;
-            }
-        }
-
         publishRoutes();
     }
 
@@ -207,15 +205,13 @@
      * The first route should be the currently selected system route.
      * For example, if there are two system routes (BT and device speaker),
      * BT will be the first route in the list.
-     *
-     * TODO: Support multiple BT devices
      */
     void publishRoutes() {
         MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
-        if (mBluetoothA2dpRoute != null) {
-            builder.addRoute(mBluetoothA2dpRoute);
-        }
         builder.addRoute(mDefaultRoute);
+        for (MediaRoute2Info route : mBluetoothRoutes) {
+            builder.addRoute(route);
+        }
         setAndNotifyProviderState(builder.build(), Collections.emptyList());
     }
 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 7f650ee..b24a938 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -18,8 +18,10 @@
 
 import static com.android.server.net.NetworkPolicyManagerService.isUidNetworkingBlockedInternal;
 
+import android.annotation.NonNull;
 import android.net.Network;
 import android.net.NetworkTemplate;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
 import android.telephony.SubscriptionPlan;
 
 import java.util.Set;
@@ -126,4 +128,12 @@
      */
     public abstract void setMeteredRestrictedPackagesAsync(
             Set<String> packageNames, int userId);
+
+    /**
+     *  Notifies that any of the {@link AbstractNetworkStatsProvider} has reached its quota
+     *  which was set through {@link AbstractNetworkStatsProvider#setLimit(String, long)}.
+     *
+     * @param tag the human readable identifier of the custom network stats provider.
+     */
+    public abstract void onStatsProviderLimitReached(@NonNull String tag);
 }
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 4a45730..d8a4655 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -75,6 +75,7 @@
 import static android.net.NetworkTemplate.MATCH_WIFI;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.net.netstats.provider.AbstractNetworkStatsProvider.QUOTA_UNLIMITED;
 import static android.os.Trace.TRACE_TAG_NETWORK;
 import static android.provider.Settings.Global.NETPOLICY_OVERRIDE_ENABLED;
 import static android.provider.Settings.Global.NETPOLICY_QUOTA_ENABLED;
@@ -391,6 +392,7 @@
     private static final int MSG_METERED_RESTRICTED_PACKAGES_CHANGED = 17;
     private static final int MSG_SET_NETWORK_TEMPLATE_ENABLED = 18;
     private static final int MSG_SUBSCRIPTION_PLANS_CHANGED = 19;
+    private static final int MSG_STATS_PROVIDER_LIMIT_REACHED = 20;
 
     private static final int UID_MSG_STATE_CHANGED = 100;
     private static final int UID_MSG_GONE = 101;
@@ -4518,6 +4520,14 @@
                     mListeners.finishBroadcast();
                     return true;
                 }
+                case MSG_STATS_PROVIDER_LIMIT_REACHED: {
+                    mNetworkStats.forceUpdate();
+                    synchronized (mNetworkPoliciesSecondLock) {
+                        updateNetworkEnabledNL();
+                        updateNotificationsNL();
+                    }
+                    return true;
+                }
                 case MSG_LIMIT_REACHED: {
                     final String iface = (String) msg.obj;
 
@@ -4573,14 +4583,18 @@
                     return true;
                 }
                 case MSG_UPDATE_INTERFACE_QUOTA: {
-                    removeInterfaceQuota((String) msg.obj);
+                    final String iface = (String) msg.obj;
                     // int params need to be stitched back into a long
-                    setInterfaceQuota((String) msg.obj,
-                            ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL));
+                    final long quota = ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL);
+                    removeInterfaceQuota(iface);
+                    setInterfaceQuota(iface, quota);
+                    mNetworkStats.setStatsProviderLimit(iface, quota);
                     return true;
                 }
                 case MSG_REMOVE_INTERFACE_QUOTA: {
-                    removeInterfaceQuota((String) msg.obj);
+                    final String iface = (String) msg.obj;
+                    removeInterfaceQuota(iface);
+                    mNetworkStats.setStatsProviderLimit(iface, QUOTA_UNLIMITED);
                     return true;
                 }
                 case MSG_RESET_FIREWALL_RULES_BY_UID: {
@@ -5235,6 +5249,12 @@
             mHandler.obtainMessage(MSG_METERED_RESTRICTED_PACKAGES_CHANGED,
                     userId, 0, packageNames).sendToTarget();
         }
+
+        @Override
+        public void onStatsProviderLimitReached(@NonNull String tag) {
+            Log.v(TAG, "onStatsProviderLimitReached: " + tag);
+            mHandler.obtainMessage(MSG_STATS_PROVIDER_LIMIT_REACHED).sendToTarget();
+        }
     }
 
     private void setMeteredRestrictedPackagesInternal(Set<String> packageNames, int userId) {
diff --git a/services/core/java/com/android/server/net/NetworkStatsFactory.java b/services/core/java/com/android/server/net/NetworkStatsFactory.java
index 3ca1803..22b01be 100644
--- a/services/core/java/com/android/server/net/NetworkStatsFactory.java
+++ b/services/core/java/com/android/server/net/NetworkStatsFactory.java
@@ -229,7 +229,7 @@
                     entry.txPackets += reader.nextLong();
                 }
 
-                stats.addValues(entry);
+                stats.addEntry(entry);
                 reader.finishLine();
             }
         } catch (NullPointerException|NumberFormatException e) {
@@ -279,7 +279,7 @@
                 entry.txBytes = reader.nextLong();
                 entry.txPackets = reader.nextLong();
 
-                stats.addValues(entry);
+                stats.addEntry(entry);
                 reader.finishLine();
             }
         } catch (NullPointerException|NumberFormatException e) {
@@ -439,7 +439,7 @@
                 if ((limitIfaces == null || ArrayUtils.contains(limitIfaces, entry.iface))
                         && (limitUid == UID_ALL || limitUid == entry.uid)
                         && (limitTag == TAG_ALL || limitTag == entry.tag)) {
-                    stats.addValues(entry);
+                    stats.addEntry(entry);
                 }
 
                 reader.finishLine();
diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
index 4843ede..6d72cb5 100644
--- a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
@@ -16,6 +16,7 @@
 
 package com.android.server.net;
 
+import android.annotation.NonNull;
 import android.net.NetworkStats;
 import android.net.NetworkTemplate;
 
@@ -34,4 +35,10 @@
 
     /** Force update of statistics. */
     public abstract void forceUpdate();
+
+    /**
+     * Set the quota limit to all registered custom network stats providers.
+     * Note that invocation of any interface will be sent to all providers.
+     */
+    public abstract void setStatsProviderLimit(@NonNull String iface, long quota);
 }
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index ec8a8e7..1dcff07 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.ACCESS_NETWORK_STATE;
 import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
 import static android.content.Intent.ACTION_SHUTDOWN;
 import static android.content.Intent.ACTION_UID_REMOVED;
 import static android.content.Intent.ACTION_USER_REMOVED;
@@ -27,6 +28,7 @@
 import static android.net.NetworkStack.checkNetworkStackPermission;
 import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
 import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.IFACE_VT;
 import static android.net.NetworkStats.INTERFACES_ALL;
 import static android.net.NetworkStats.METERED_ALL;
 import static android.net.NetworkStats.ROAMING_ALL;
@@ -70,6 +72,7 @@
 import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.app.usage.NetworkStatsManager;
@@ -96,6 +99,9 @@
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.net.TrafficStats;
+import android.net.netstats.provider.INetworkStatsProvider;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
 import android.os.BestClock;
 import android.os.Binder;
 import android.os.DropBoxManager;
@@ -108,6 +114,7 @@
 import android.os.Message;
 import android.os.Messenger;
 import android.os.PowerManager;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -175,7 +182,7 @@
      * This avoids firing the global alert too often on devices with high transfer speeds and
      * high quota.
      */
-    private static final int PERFORM_POLL_DELAY_MS = 1000;
+    private static final int DEFAULT_PERFORM_POLL_DELAY_MS = 1000;
 
     private static final String TAG_NETSTATS_ERROR = "netstats_error";
 
@@ -211,13 +218,15 @@
     /**
      * Virtual network interface for video telephony. This is for VT data usage counting purpose.
      */
-    public static final String VT_INTERFACE = "vt_data0";
+    // TODO: Remove this after no one is using it.
+    public static final String VT_INTERFACE = NetworkStats.IFACE_VT;
 
     /**
      * Settings that can be changed externally.
      */
     public interface NetworkStatsSettings {
         public long getPollInterval();
+        public long getPollDelay();
         public boolean getSampleEnabled();
         public boolean getAugmentEnabled();
 
@@ -246,6 +255,7 @@
     }
 
     private final Object mStatsLock = new Object();
+    private final Object mStatsProviderLock = new Object();
 
     /** Set of currently active ifaces. */
     @GuardedBy("mStatsLock")
@@ -270,6 +280,9 @@
     private final DropBoxNonMonotonicObserver mNonMonotonicObserver =
             new DropBoxNonMonotonicObserver();
 
+    private final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList =
+            new RemoteCallbackList<>();
+
     @GuardedBy("mStatsLock")
     private NetworkStatsRecorder mDevRecorder;
     @GuardedBy("mStatsLock")
@@ -500,9 +513,9 @@
     }
 
     /**
-     * Register for a global alert that is delivered through
-     * {@link INetworkManagementEventObserver} once a threshold amount of data
-     * has been transferred.
+     * Register for a global alert that is delivered through {@link INetworkManagementEventObserver}
+     * or {@link NetworkStatsProviderCallback#onAlertReached()} once a threshold amount of data has
+     * been transferred.
      */
     private void registerGlobalAlert() {
         try {
@@ -512,6 +525,7 @@
         } catch (RemoteException e) {
             // ignored; service lives in system_server
         }
+        invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.setAlert(mGlobalAlertBytes));
     }
 
     @Override
@@ -712,7 +726,7 @@
         final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null);
 
         final NetworkStats stats = new NetworkStats(end - start, 1);
-        stats.addValues(new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE,
+        stats.addEntry(new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE,
                 METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, entry.rxBytes, entry.rxPackets,
                 entry.txBytes, entry.txPackets, entry.operations));
         return stats;
@@ -801,8 +815,7 @@
     @Override
     public void incrementOperationCount(int uid, int tag, int operationCount) {
         if (Binder.getCallingUid() != uid) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.UPDATE_DEVICE_STATS, TAG);
+            mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
         }
 
         if (operationCount < 0) {
@@ -1093,7 +1106,7 @@
     /**
      * Observer that watches for {@link INetworkManagementService} alerts.
      */
-    private INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() {
+    private final INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() {
         @Override
         public void limitReached(String limitName, String iface) {
             // only someone like NMS should be calling us
@@ -1104,7 +1117,7 @@
                 // such a call pending; UID stats are handled during normal polling interval.
                 if (!mHandler.hasMessages(MSG_PERFORM_POLL_REGISTER_ALERT)) {
                     mHandler.sendEmptyMessageDelayed(MSG_PERFORM_POLL_REGISTER_ALERT,
-                            PERFORM_POLL_DELAY_MS);
+                            mSettings.getPollDelay());
                 }
             }
         }
@@ -1179,8 +1192,8 @@
                                 ident.getSubType(), ident.getSubscriberId(), ident.getNetworkId(),
                                 ident.getRoaming(), true /* metered */,
                                 true /* onDefaultNetwork */);
-                        findOrCreateNetworkIdentitySet(mActiveIfaces, VT_INTERFACE).add(vtIdent);
-                        findOrCreateNetworkIdentitySet(mActiveUidIfaces, VT_INTERFACE).add(vtIdent);
+                        findOrCreateNetworkIdentitySet(mActiveIfaces, IFACE_VT).add(vtIdent);
+                        findOrCreateNetworkIdentitySet(mActiveUidIfaces, IFACE_VT).add(vtIdent);
                     }
 
                     if (isMobile) {
@@ -1250,6 +1263,14 @@
         xtSnapshot.combineAllValues(tetherSnapshot);
         devSnapshot.combineAllValues(tetherSnapshot);
 
+        // Snapshot for dev/xt stats from all custom stats providers. Counts per-interface data
+        // from stats providers that isn't already counted by dev and XT stats.
+        Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotStatsProvider");
+        final NetworkStats providersnapshot = getNetworkStatsFromProviders(STATS_PER_IFACE);
+        Trace.traceEnd(TRACE_TAG_NETWORK);
+        xtSnapshot.combineAllValues(providersnapshot);
+        devSnapshot.combineAllValues(providersnapshot);
+
         // For xt/dev, we pass a null VPN array because usage is aggregated by UID, so VPN traffic
         // can't be reattributed to responsible apps.
         Trace.traceBegin(TRACE_TAG_NETWORK, "recordDev");
@@ -1353,6 +1374,10 @@
             performSampleLocked();
         }
 
+        // request asynchronous stats update from all providers for next poll.
+        // TODO: request with a valid token.
+        invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.requestStatsUpdate(0 /* unused */));
+
         // finally, dispatch updated event to any listeners
         final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
         updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -1474,6 +1499,12 @@
         public void forceUpdate() {
             NetworkStatsService.this.forceUpdate();
         }
+
+        @Override
+        public void setStatsProviderLimit(@NonNull String iface, long quota) {
+            Slog.v(TAG, "setStatsProviderLimit(" + iface + "," + quota + ")");
+            invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.setLimit(iface, quota));
+        }
     }
 
     @Override
@@ -1688,6 +1719,12 @@
             uidSnapshot.combineAllValues(vtStats);
         }
 
+        // get a stale copy of uid stats snapshot provided by providers.
+        final NetworkStats providerStats = getNetworkStatsFromProviders(STATS_PER_UID);
+        providerStats.filter(UID_ALL, ifaces, TAG_ALL);
+        mStatsFactory.apply464xlatAdjustments(uidSnapshot, providerStats, mUseBpfTrafficStats);
+        uidSnapshot.combineAllValues(providerStats);
+
         uidSnapshot.combineAllValues(mUidOperations);
 
         return uidSnapshot;
@@ -1724,6 +1761,152 @@
         }
     }
 
+    /**
+     * Registers a custom provider of {@link android.net.NetworkStats} to combine the network
+     * statistics that cannot be seen by the kernel to system. To unregister, invoke the
+     * {@code unregister()} of the returned callback.
+     *
+     * @param tag a human readable identifier of the custom network stats provider.
+     * @param provider the binder interface of
+     *                 {@link android.net.netstats.provider.AbstractNetworkStatsProvider} that
+     *                 needs to be registered to the system.
+     *
+     * @return a binder interface of
+     *         {@link android.net.netstats.provider.NetworkStatsProviderCallback}, which can be
+     *         used to report events to the system.
+     */
+    public @NonNull INetworkStatsProviderCallback registerNetworkStatsProvider(
+            @NonNull String tag, @NonNull INetworkStatsProvider provider) {
+        mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
+        Objects.requireNonNull(provider, "provider is null");
+        Objects.requireNonNull(tag, "tag is null");
+        try {
+            NetworkStatsProviderCallbackImpl callback = new NetworkStatsProviderCallbackImpl(
+                            tag, provider, mAlertObserver, mStatsProviderCbList);
+            mStatsProviderCbList.register(callback);
+            Log.d(TAG, "registerNetworkStatsProvider from " + callback.mTag + " uid/pid="
+                    + getCallingUid() + "/" + getCallingPid());
+            return callback;
+        } catch (RemoteException e) {
+            Log.e(TAG, "registerNetworkStatsProvider failed", e);
+        }
+        return null;
+    }
+
+    // Collect stats from local cache of providers.
+    private @NonNull NetworkStats getNetworkStatsFromProviders(int how) {
+        final NetworkStats ret = new NetworkStats(0L, 0);
+        invokeForAllStatsProviderCallbacks((cb) -> ret.combineAllValues(cb.getCachedStats(how)));
+        return ret;
+    }
+
+    @FunctionalInterface
+    private interface ThrowingConsumer<S, T extends Throwable> {
+        void accept(S s) throws T;
+    }
+
+    private void invokeForAllStatsProviderCallbacks(
+            @NonNull ThrowingConsumer<NetworkStatsProviderCallbackImpl, RemoteException> task) {
+        synchronized (mStatsProviderCbList) {
+            final int length = mStatsProviderCbList.beginBroadcast();
+            try {
+                for (int i = 0; i < length; i++) {
+                    final NetworkStatsProviderCallbackImpl cb =
+                            mStatsProviderCbList.getBroadcastItem(i);
+                    try {
+                        task.accept(cb);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Fail to broadcast to provider: " + cb.mTag, e);
+                    }
+                }
+            } finally {
+                mStatsProviderCbList.finishBroadcast();
+            }
+        }
+    }
+
+    private static class NetworkStatsProviderCallbackImpl extends INetworkStatsProviderCallback.Stub
+            implements IBinder.DeathRecipient {
+        @NonNull final String mTag;
+        @NonNull private final Object mProviderStatsLock = new Object();
+        @NonNull final INetworkStatsProvider mProvider;
+        @NonNull final INetworkManagementEventObserver mAlertObserver;
+        @NonNull final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList;
+
+        @GuardedBy("mProviderStatsLock")
+        // STATS_PER_IFACE and STATS_PER_UID
+        private final NetworkStats mIfaceStats = new NetworkStats(0L, 0);
+        @GuardedBy("mProviderStatsLock")
+        private final NetworkStats mUidStats = new NetworkStats(0L, 0);
+
+        NetworkStatsProviderCallbackImpl(
+                @NonNull String tag, @NonNull INetworkStatsProvider provider,
+                @NonNull INetworkManagementEventObserver alertObserver,
+                @NonNull RemoteCallbackList<NetworkStatsProviderCallbackImpl> cbList)
+                throws RemoteException {
+            mTag = tag;
+            mProvider = provider;
+            mProvider.asBinder().linkToDeath(this, 0);
+            mAlertObserver = alertObserver;
+            mStatsProviderCbList = cbList;
+        }
+
+        @NonNull
+        public NetworkStats getCachedStats(int how) {
+            synchronized (mProviderStatsLock) {
+                NetworkStats stats;
+                switch (how) {
+                    case STATS_PER_IFACE:
+                        stats = mIfaceStats;
+                        break;
+                    case STATS_PER_UID:
+                        stats = mUidStats;
+                        break;
+                    default:
+                        throw new IllegalArgumentException("Invalid type: " + how);
+                }
+                // Return a defensive copy instead of local reference.
+                return stats.clone();
+            }
+        }
+
+        @Override
+        public void onStatsUpdated(int token, @Nullable NetworkStats ifaceStats,
+                @Nullable NetworkStats uidStats) {
+            // TODO: 1. Use token to map ifaces to correct NetworkIdentity.
+            //       2. Store the difference and store it directly to the recorder.
+            synchronized (mProviderStatsLock) {
+                if (ifaceStats != null) mIfaceStats.combineAllValues(ifaceStats);
+                if (uidStats != null) mUidStats.combineAllValues(uidStats);
+            }
+        }
+
+        @Override
+        public void onAlertReached() throws RemoteException {
+            mAlertObserver.limitReached(LIMIT_GLOBAL_ALERT, null /* unused */);
+        }
+
+        @Override
+        public void onLimitReached() {
+            Log.d(TAG, mTag + ": onLimitReached");
+            LocalServices.getService(NetworkPolicyManagerInternal.class)
+                    .onStatsProviderLimitReached(mTag);
+        }
+
+        @Override
+        public void binderDied() {
+            Log.d(TAG, mTag + ": binderDied");
+            mStatsProviderCbList.unregister(this);
+        }
+
+        @Override
+        public void unregister() {
+            Log.d(TAG, mTag + ": unregister");
+            mStatsProviderCbList.unregister(this);
+        }
+
+    }
+
     @VisibleForTesting
     static class HandlerCallback implements Handler.Callback {
         private final NetworkStatsService mService;
@@ -1813,6 +1996,10 @@
             return getGlobalLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS);
         }
         @Override
+        public long getPollDelay() {
+            return DEFAULT_PERFORM_POLL_DELAY_MS;
+        }
+        @Override
         public long getGlobalAlertBytes(long def) {
             return getGlobalLong(NETSTATS_GLOBAL_ALERT_BYTES, def);
         }
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index d66fd57..da2ca9b 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -43,7 +43,8 @@
         }
 
         record.updateNotificationChannel(mConfig.getNotificationChannel(record.sbn.getPackageName(),
-                record.sbn.getUid(), record.getChannel().getId(), false));
+                record.sbn.getUid(), record.getChannel().getId(),
+                record.getNotification().getShortcutId(), false));
 
         return null;
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a4f4d07..0e08033 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -25,6 +25,7 @@
 import static android.app.Notification.FLAG_NO_CLEAR;
 import static android.app.Notification.FLAG_ONGOING_EVENT;
 import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
+import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
 import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
@@ -236,6 +237,7 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.TriPredicate;
 import com.android.server.DeviceIdleInternal;
@@ -1008,12 +1010,14 @@
             if (clearEffects) {
                 clearEffects();
             }
+            mAssistants.onPanelRevealed(items);
         }
 
         @Override
         public void onPanelHidden() {
             MetricsLogger.hidden(getContext(), MetricsEvent.NOTIFICATION_PANEL);
             EventLogTags.writeNotificationPanelHidden();
+            mAssistants.onPanelHidden();
         }
 
         @Override
@@ -1060,6 +1064,7 @@
                         reportSeen(r);
                     }
                     r.setVisibility(true, nv.rank, nv.count);
+                    mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, true);
                     boolean isHun = (nv.location
                             == NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
                     // hasBeenVisiblyExpanded must be called after updating the expansion state of
@@ -1078,6 +1083,7 @@
                     NotificationRecord r = mNotificationsByKey.get(nv.key);
                     if (r == null) continue;
                     r.setVisibility(false, nv.rank, nv.count);
+                    mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, false);
                     nv.recycle();
                 }
             }
@@ -2219,8 +2225,8 @@
         maybeNotifyChannelOwner(pkg, uid, preUpdate, channel);
 
         if (!fromListener) {
-            final NotificationChannel modifiedChannel =
-                    mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+            final NotificationChannel modifiedChannel = mPreferencesHelper.getNotificationChannel(
+                    pkg, uid, channel.getId(), false);
             mListeners.notifyNotificationChannelChanged(
                     pkg, UserHandle.getUserHandleForUid(uid),
                     modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
@@ -2481,7 +2487,7 @@
                     .setUid(r.sbn.getUid())
                     .setChannelId(r.getChannel().getId())
                     .setChannelName(r.getChannel().getName().toString())
-                    .setPostedTimeMs(r.sbn.getPostTime())
+                    .setPostedTimeMs(System.currentTimeMillis())
                     .setTitle(getHistoryTitle(r.getNotification()))
                     .setText(getHistoryText(
                             r.sbn.getPackageContext(getContext()), r.getNotification()))
@@ -3017,21 +3023,43 @@
 
         @Override
         public void createNotificationChannels(String pkg,
-                ParceledListSlice channelsList) throws RemoteException {
+                ParceledListSlice channelsList) {
             checkCallerIsSystemOrSameApp(pkg);
             createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList);
         }
 
         @Override
         public void createNotificationChannelsForPackage(String pkg, int uid,
-                ParceledListSlice channelsList) throws RemoteException {
-            checkCallerIsSystem();
+                ParceledListSlice channelsList) {
+            enforceSystemOrSystemUI("only system can call this");
             createNotificationChannelsImpl(pkg, uid, channelsList);
         }
 
         @Override
+        public void createConversationNotificationChannelForPackage(String pkg, int uid,
+                NotificationChannel parentChannel, String conversationId) {
+            enforceSystemOrSystemUI("only system can call this");
+            Preconditions.checkNotNull(parentChannel);
+            Preconditions.checkNotNull(conversationId);
+            String parentId = parentChannel.getId();
+            NotificationChannel conversationChannel = parentChannel;
+            conversationChannel.setId(String.format(
+                    CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId));
+            conversationChannel.setConversationId(parentId, conversationId);
+            createNotificationChannelsImpl(
+                    pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)));
+        }
+
+        @Override
         public NotificationChannel getNotificationChannel(String callingPkg, int userId,
                 String targetPkg, String channelId) {
+            return getConversationNotificationChannel(
+                    callingPkg, userId, targetPkg, channelId, null);
+        }
+
+        @Override
+        public NotificationChannel getConversationNotificationChannel(String callingPkg, int userId,
+                String targetPkg, String channelId, String conversationId) {
             if (canNotifyAsPackage(callingPkg, targetPkg, userId)
                     || isCallingUidSystem()) {
                 int targetUid = -1;
@@ -3041,7 +3069,8 @@
                     /* ignore */
                 }
                 return mPreferencesHelper.getNotificationChannel(
-                        targetPkg, targetUid, channelId, false /* includeDeleted */);
+                        targetPkg, targetUid, channelId, conversationId,
+                        false /* includeDeleted */);
             }
             throw new SecurityException("Pkg " + callingPkg
                     + " cannot read channels for " + targetPkg + " in " + userId);
@@ -3072,6 +3101,30 @@
         }
 
         @Override
+        public void deleteConversationNotificationChannels(String pkg, int uid,
+                String conversationId) {
+            checkCallerIsSystem();
+            final int callingUid = Binder.getCallingUid();
+            List<NotificationChannel> channels =
+                    mPreferencesHelper.getNotificationChannelsByConversationId(
+                            pkg, uid, conversationId);
+            if (!channels.isEmpty()) {
+                for (NotificationChannel nc : channels) {
+                    cancelAllNotificationsInt(MY_UID, MY_PID, pkg, nc.getId(), 0, 0, true,
+                            UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null);
+                    mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, nc.getId());
+                    mListeners.notifyNotificationChannelChanged(pkg,
+                            UserHandle.getUserHandleForUid(callingUid),
+                            mPreferencesHelper.getNotificationChannel(
+                                    pkg, callingUid, nc.getId(), true),
+                            NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
+                }
+                handleSavePolicyFile();
+            }
+        }
+
+
+        @Override
         public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) {
             checkCallerIsSystemOrSameApp(pkg);
             return mPreferencesHelper.getNotificationChannelGroupWithChannels(
@@ -5203,7 +5256,8 @@
             channelId = (new Notification.TvExtender(notification)).getChannelId();
         }
         final NotificationChannel channel = mPreferencesHelper.getNotificationChannel(pkg,
-                notificationUid, channelId, false /* includeDeleted */);
+                notificationUid, channelId, notification.getShortcutId(),
+                false /* includeDeleted */);
         if (channel == null) {
             final String noChannelStr = "No Channel found for "
                     + "pkg=" + pkg
@@ -8296,6 +8350,32 @@
             }
         }
 
+        protected void onPanelRevealed(int items) {
+            for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
+                mHandler.post(() -> {
+                    final INotificationListener assistant = (INotificationListener) info.service;
+                    try {
+                        assistant.onPanelRevealed(items);
+                    } catch (RemoteException ex) {
+                        Slog.e(TAG, "unable to notify assistant (panel revealed): " + info, ex);
+                    }
+                });
+            }
+        }
+
+        protected void onPanelHidden() {
+            for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
+                mHandler.post(() -> {
+                    final INotificationListener assistant = (INotificationListener) info.service;
+                    try {
+                        assistant.onPanelHidden();
+                    } catch (RemoteException ex) {
+                        Slog.e(TAG, "unable to notify assistant (panel hidden): " + info, ex);
+                    }
+                });
+            }
+        }
+
         boolean hasUserSet(int userId) {
             synchronized (mLock) {
                 return mUserSetMap.getOrDefault(userId, false);
@@ -8363,6 +8443,24 @@
         }
 
         @GuardedBy("mNotificationLock")
+        void notifyAssistantVisibilityChangedLocked(
+                final StatusBarNotification sbn,
+                final boolean isVisible) {
+            final String key = sbn.getKey();
+            Slog.d(TAG, "notifyAssistantVisibilityChangedLocked: " + key);
+            notifyAssistantLocked(
+                    sbn,
+                    false /* sameUserOnly */,
+                    (assistant, sbnHolder) -> {
+                        try {
+                            assistant.onNotificationVisibilityChanged(key, isVisible);
+                        } catch (RemoteException ex) {
+                            Slog.e(TAG, "unable to notify assistant (visible): " + assistant, ex);
+                        }
+                    });
+        }
+
+        @GuardedBy("mNotificationLock")
         void notifyAssistantExpansionChangedLocked(
                 final StatusBarNotification sbn,
                 final boolean isUserAction,
diff --git a/services/core/java/com/android/server/notification/OWNERS b/services/core/java/com/android/server/notification/OWNERS
new file mode 100644
index 0000000..5a19656
--- /dev/null
+++ b/services/core/java/com/android/server/notification/OWNERS
@@ -0,0 +1,4 @@
+dsandler@android.com
+juliacr@google.com
+beverlyt@google.com
+pixel@google.com
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index cdb0a17..92fcb7f 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -770,6 +770,13 @@
                 channel.setShowBadge(false);
             }
             channel.setOriginalImportance(channel.getImportance());
+
+            // validate parent
+            if (channel.getParentChannelId() != null) {
+                Preconditions.checkArgument(r.channels.containsKey(channel.getParentChannelId()),
+                        "Tried to create a conversation channel without a preexisting parent");
+            }
+
             r.channels.put(channel.getId(), channel);
             if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
                 updateChannelsBypassingDnd(mContext.getUserId());
@@ -851,6 +858,13 @@
     public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
             boolean includeDeleted) {
         Objects.requireNonNull(pkg);
+        return getNotificationChannel(pkg, uid, channelId, null, includeDeleted);
+    }
+
+    @Override
+    public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
+            String conversationId, boolean includeDeleted) {
+        Preconditions.checkNotNull(pkg);
         synchronized (mPackagePreferences) {
             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
             if (r == null) {
@@ -859,14 +873,51 @@
             if (channelId == null) {
                 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
             }
-            final NotificationChannel nc = r.channels.get(channelId);
-            if (nc != null && (includeDeleted || !nc.isDeleted())) {
-                return nc;
+            if (conversationId == null) {
+                final NotificationChannel nc = r.channels.get(channelId);
+                if (nc != null && (includeDeleted || !nc.isDeleted())) {
+                    return nc;
+                }
+            } else {
+                // look for an automatically created conversation specific channel
+                return findConversationChannel(r, channelId, conversationId, includeDeleted);
             }
             return null;
         }
     }
 
+    private NotificationChannel findConversationChannel(PackagePreferences p, String parentId,
+            String conversationId, boolean includeDeleted) {
+        for (NotificationChannel nc : p.channels.values()) {
+            if (conversationId.equals(nc.getConversationId())
+                    && parentId.equals(nc.getParentChannelId())
+                    && (includeDeleted || !nc.isDeleted())) {
+                return nc;
+            }
+        }
+        return null;
+    }
+
+    public List<NotificationChannel> getNotificationChannelsByConversationId(String pkg, int uid,
+            String conversationId) {
+        Preconditions.checkNotNull(pkg);
+        Preconditions.checkNotNull(conversationId);
+        List<NotificationChannel> channels = new ArrayList<>();
+        synchronized (mPackagePreferences) {
+            PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
+            if (r == null) {
+                return channels;
+            }
+            for (NotificationChannel nc : r.channels.values()) {
+                if (conversationId.equals(nc.getConversationId())
+                        && !nc.isDeleted()) {
+                    channels.add(nc);
+                }
+            }
+            return channels;
+        }
+    }
+
     @Override
     public void deleteNotificationChannel(String pkg, int uid, String channelId) {
         synchronized (mPackagePreferences) {
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 7816f36..4b044c1 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -41,10 +41,15 @@
             int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty);
     boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel,
             boolean fromTargetApp, boolean hasDndAccess);
-    void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser);
-    NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
+    void updateNotificationChannel(String pkg, int uid, NotificationChannel channel,
+            boolean fromUser);
+    NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
+            boolean includeDeleted);
+    NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
+            String conversationId, boolean includeDeleted);
     void deleteNotificationChannel(String pkg, int uid, String channelId);
     void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
     void permanentlyDeleteNotificationChannels(String pkg, int uid);
-    ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, boolean includeDeleted);
+    ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
+            boolean includeDeleted);
 }
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index b782ca96..3c31f6a 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -83,6 +83,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -925,7 +926,7 @@
     /**
      * Updates the target packages' set of enabled overlays in PackageManager.
      */
-    private void updateOverlayPaths(int userId, List<String> targetPackageNames) {
+    private ArrayList<String> updateOverlayPaths(int userId, List<String> targetPackageNames) {
         try {
             traceBegin(TRACE_TAG_RRO, "OMS#updateOverlayPaths " + targetPackageNames);
             if (DEBUG) {
@@ -955,6 +956,7 @@
                 }
             }
 
+            final HashSet<String> updatedPackages = new HashSet<>();
             final int n = targetPackageNames.size();
             for (int i = 0; i < n; i++) {
                 final String targetPackageName = targetPackageNames.get(i);
@@ -965,11 +967,13 @@
                 }
 
                 if (!pm.setEnabledOverlayPackages(
-                        userId, targetPackageName, pendingChanges.get(targetPackageName))) {
+                        userId, targetPackageName, pendingChanges.get(targetPackageName),
+                        updatedPackages)) {
                     Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d",
                             targetPackageName, userId));
                 }
             }
+            return new ArrayList<>(updatedPackages);
         } finally {
             traceEnd(TRACE_TAG_RRO);
         }
@@ -980,10 +984,10 @@
     }
 
     private void updateAssets(final int userId, List<String> targetPackageNames) {
-        updateOverlayPaths(userId, targetPackageNames);
         final IActivityManager am = ActivityManager.getService();
         try {
-            am.scheduleApplicationInfoChanged(targetPackageNames, userId);
+            final ArrayList<String> updatedPaths = updateOverlayPaths(userId, targetPackageNames);
+            am.scheduleApplicationInfoChanged(updatedPaths, userId);
         } catch (RemoteException e) {
             // Intentionally left empty.
         }
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 019c952..9623542 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -685,7 +685,7 @@
         // Static RROs targeting to "android", ie framework-res.apk, are handled by native layers.
         if (targetPackage != null && overlayPackage != null
                 && !("android".equals(targetPackageName)
-                        && overlayPackage.isStaticOverlayPackage())) {
+                    && overlayPackage.isStaticOverlayPackage())) {
             mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
         }
 
@@ -703,9 +703,9 @@
         if (currentState != newState) {
             if (DEBUG) {
                 Slog.d(TAG, String.format("%s:%d: %s -> %s",
-                            overlayPackageName, userId,
-                            OverlayInfo.stateToString(currentState),
-                            OverlayInfo.stateToString(newState)));
+                        overlayPackageName, userId,
+                        OverlayInfo.stateToString(currentState),
+                        OverlayInfo.stateToString(newState)));
             }
             modified |= mSettings.setState(overlayPackageName, userId, newState);
         }
diff --git a/services/core/java/com/android/server/om/TEST_MAPPING b/services/core/java/com/android/server/om/TEST_MAPPING
new file mode 100644
index 0000000..52163a0
--- /dev/null
+++ b/services/core/java/com/android/server/om/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.om."
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 307a07b..2807909 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -32,10 +32,13 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
+import android.content.pm.parsing.AndroidPackage;
 import android.os.Environment;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.sysprop.ApexProperties;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Singleton;
 import android.util.Slog;
 
@@ -44,15 +47,19 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.IndentingPrintWriter;
 
+import com.google.android.collect.Lists;
+
 import java.io.File;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -97,12 +104,27 @@
      * Minimal information about APEX mount points and the original APEX package they refer to.
      */
     static class ActiveApexInfo {
+        @Nullable public final String apexModuleName;
         public final File apexDirectory;
-        public final File preinstalledApexPath;
+        public final File preInstalledApexPath;
 
-        private ActiveApexInfo(File apexDirectory, File preinstalledApexPath) {
+        private ActiveApexInfo(File apexDirectory, File preInstalledApexPath) {
+            this(null, apexDirectory, preInstalledApexPath);
+        }
+
+        private ActiveApexInfo(@Nullable String apexModuleName, File apexDirectory,
+                File preInstalledApexPath) {
+            this.apexModuleName = apexModuleName;
             this.apexDirectory = apexDirectory;
-            this.preinstalledApexPath = preinstalledApexPath;
+            this.preInstalledApexPath = preInstalledApexPath;
+        }
+
+        private ActiveApexInfo(ApexInfo apexInfo) {
+            this(
+                    apexInfo.moduleName,
+                    new File(Environment.getApexDirectory() + File.separator
+                            + apexInfo.moduleName),
+                    new File(apexInfo.preinstalledModulePath));
         }
     }
 
@@ -232,6 +254,17 @@
     abstract boolean uninstallApex(String apexPackagePath);
 
     /**
+     * Registers an APK package as an embedded apk of apex.
+     */
+    abstract void registerApkInApex(AndroidPackage pkg);
+
+    /**
+     * Returns list of {@code packageName} of apks inside the given apex.
+     * @param apexPackageName Package name of the apk container of apex
+     */
+    abstract List<String> getApksInApex(String apexPackageName);
+
+    /**
      * Dumps various state information to the provided {@link PrintWriter} object.
      *
      * @param pw the {@link PrintWriter} object to send information to.
@@ -255,16 +288,33 @@
     static class ApexManagerImpl extends ApexManager {
         private final IApexService mApexService;
         private final Object mLock = new Object();
+
+        @GuardedBy("mLock")
+        private Set<ActiveApexInfo> mActiveApexInfosCache;
+
         /**
-         * A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code
-         * AndroidManifest.xml}
-         *
-         * <p>Note that key of this map is {@code packageName} field of the corresponding {@code
-         * AndroidManifest.xml}.
-          */
+         * Contains the list of {@code packageName}s of apks-in-apex for given
+         * {@code apexModuleName}. See {@link #mPackageNameToApexModuleName} to understand the
+         * difference between {@code packageName} and {@code apexModuleName}.
+         */
+        @GuardedBy("mLock")
+        private Map<String, List<String>> mApksInApex = new ArrayMap<>();
+
         @GuardedBy("mLock")
         private List<PackageInfo> mAllPackagesCache;
 
+        /**
+         * An APEX is a file format that delivers the apex-payload wrapped in an apk container. The
+         * apk container has a reference name, called {@code packageName}, which is found inside the
+         * {@code AndroidManifest.xml}. The apex payload inside the container also has a reference
+         * name, called {@code apexModuleName}, which is found in {@code apex_manifest.json} file.
+         *
+         * {@link #mPackageNameToApexModuleName} contains the mapping from {@code packageName} of
+         * the apk container to {@code apexModuleName} of the apex-payload inside.
+         */
+        @GuardedBy("mLock")
+        private Map<String, String> mPackageNameToApexModuleName;
+
         ApexManagerImpl(IApexService apexService) {
             mApexService = apexService;
         }
@@ -291,18 +341,25 @@
 
         @Override
         List<ActiveApexInfo> getActiveApexInfos() {
-            try {
-                return Arrays.stream(mApexService.getActivePackages())
-                        .map(apexInfo -> new ActiveApexInfo(
-                                new File(
-                                Environment.getApexDirectory() + File.separator
-                                        + apexInfo.moduleName),
-                                new File(apexInfo.preinstalledModulePath))).collect(
-                                Collectors.toList());
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Unable to retrieve packages from apexservice", e);
+            synchronized (mLock) {
+                if (mActiveApexInfosCache == null) {
+                    try {
+                        mActiveApexInfosCache = new ArraySet<>();
+                        final ApexInfo[] activePackages = mApexService.getActivePackages();
+                        for (int i = 0; i < activePackages.length; i++) {
+                            ApexInfo apexInfo = activePackages[i];
+                            mActiveApexInfosCache.add(new ActiveApexInfo(apexInfo));
+                        }
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Unable to retrieve packages from apexservice", e);
+                    }
+                }
+                if (mActiveApexInfosCache != null) {
+                    return new ArrayList<>(mActiveApexInfosCache);
+                } else {
+                    return Collections.emptyList();
+                }
             }
-            return Collections.emptyList();
         }
 
         @Override
@@ -318,6 +375,33 @@
             }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
         }
 
+        private void populatePackageNameToApexModuleNameIfNeeded() {
+            synchronized (mLock) {
+                if (mPackageNameToApexModuleName != null) {
+                    return;
+                }
+                try {
+                    mPackageNameToApexModuleName = new ArrayMap<>();
+                    final ApexInfo[] allPkgs = mApexService.getAllPackages();
+                    for (int i = 0; i < allPkgs.length; i++) {
+                        ApexInfo ai = allPkgs[i];
+                        PackageParser.PackageLite pkgLite;
+                        try {
+                            File apexFile = new File(ai.modulePath);
+                            pkgLite = PackageParser.parsePackageLite(apexFile, 0);
+                        } catch (PackageParser.PackageParserException pe) {
+                            throw new IllegalStateException("Unable to parse: "
+                                    + ai.modulePath, pe);
+                        }
+                        mPackageNameToApexModuleName.put(pkgLite.packageName, ai.moduleName);
+                    }
+                } catch (RemoteException re) {
+                    Slog.e(TAG, "Unable to retrieve packages from apexservice: ", re);
+                    throw new RuntimeException(re);
+                }
+            }
+        }
+
         private void populateAllPackagesCacheIfNeeded() {
             synchronized (mLock) {
                 if (mAllPackagesCache != null) {
@@ -366,7 +450,6 @@
                             }
                             factoryPackagesSet.add(packageInfo.packageName);
                         }
-
                     }
                 } catch (RemoteException re) {
                     Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
@@ -533,6 +616,36 @@
             }
         }
 
+        @Override
+        void registerApkInApex(AndroidPackage pkg) {
+            synchronized (mLock) {
+                final Iterator<ActiveApexInfo> it = mActiveApexInfosCache.iterator();
+                while (it.hasNext()) {
+                    final ActiveApexInfo aai = it.next();
+                    if (pkg.getBaseCodePath().startsWith(aai.apexDirectory.getAbsolutePath())) {
+                        List<String> apks = mApksInApex.get(aai.apexModuleName);
+                        if (apks == null) {
+                            apks = Lists.newArrayList();
+                            mApksInApex.put(aai.apexModuleName, apks);
+                        }
+                        apks.add(pkg.getPackageName());
+                    }
+                }
+            }
+        }
+
+        @Override
+        List<String> getApksInApex(String apexPackageName) {
+            populatePackageNameToApexModuleNameIfNeeded();
+            synchronized (mLock) {
+                String moduleName = mPackageNameToApexModuleName.get(apexPackageName);
+                if (moduleName == null) {
+                    return Collections.emptyList();
+                }
+                return mApksInApex.getOrDefault(moduleName, Collections.emptyList());
+            }
+        }
+
         /**
          * Dump information about the packages contained in a particular cache
          * @param packagesCache the cache to print information about.
@@ -614,7 +727,6 @@
      * updating APEX packages.
      */
     private static final class ApexManagerFlattenedApex extends ApexManager {
-
         @Override
         List<ActiveApexInfo> getActiveApexInfos() {
             // There is no apexd running in case of flattened apex
@@ -721,6 +833,16 @@
         }
 
         @Override
+        void registerApkInApex(AndroidPackage pkg) {
+            // No-op
+        }
+
+        @Override
+        List<String> getApksInApex(String apexPackageName) {
+            return Collections.emptyList();
+        }
+
+        @Override
         void dump(PrintWriter pw, String packageName) {
             // No-op
         }
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index b25e1e2..ed71399 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -15,35 +15,45 @@
  */
 package com.android.server.pm;
 
+import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
+import android.Manifest;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions;
+import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.IApplicationThread;
 import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ICrossProfileApps;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.stats.devicepolicy.DevicePolicyEnums;
 import android.text.TextUtils;
+import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
+import com.android.server.appop.AppOpsService;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import java.util.ArrayList;
@@ -55,6 +65,9 @@
 
     private Context mContext;
     private Injector mInjector;
+    private AppOpsService mAppOpsService;
+    private final DevicePolicyManagerInternal mDpmi;
+    private final IPackageManager mIpm;
 
     public CrossProfileAppsServiceImpl(Context context) {
         this(context, new InjectorImpl(context));
@@ -64,6 +77,8 @@
     CrossProfileAppsServiceImpl(Context context, Injector injector) {
         mContext = context;
         mInjector = injector;
+        mIpm = AppGlobals.getPackageManager();
+        mDpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
     }
 
     @Override
@@ -153,6 +168,63 @@
                 userId);
     }
 
+    @Override
+    public boolean canRequestInteractAcrossProfiles(String callingPackage) {
+        Objects.requireNonNull(callingPackage);
+        verifyCallingPackage(callingPackage);
+
+        final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(
+                callingPackage, mInjector.getCallingUserId());
+        if (targetUserProfiles.isEmpty()) {
+            return false;
+        }
+
+        if (!hasRequestedAppOpPermission(
+                AppOpsManager.opToPermission(OP_INTERACT_ACROSS_PROFILES), callingPackage)) {
+            return false;
+        }
+        return isCrossProfilePackageWhitelisted(callingPackage);
+    }
+
+    private boolean hasRequestedAppOpPermission(String permission, String packageName) {
+        try {
+            String[] packages = mIpm.getAppOpPermissionPackages(permission);
+            return ArrayUtils.contains(packages, packageName);
+        } catch (RemoteException exc) {
+            Slog.e(TAG, "PackageManager dead. Cannot get permission info");
+            return false;
+        }
+    }
+
+    @Override
+    public boolean canInteractAcrossProfiles(String callingPackage) {
+        Objects.requireNonNull(callingPackage);
+        verifyCallingPackage(callingPackage);
+
+        final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(
+                callingPackage, mInjector.getCallingUserId());
+        if (targetUserProfiles.isEmpty()) {
+            return false;
+        }
+
+        final int callingUid = mInjector.getCallingUid();
+        return isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
+                || isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS, callingUid)
+                || isPermissionGranted(Manifest.permission.INTERACT_ACROSS_PROFILES, callingUid)
+                || AppOpsManager.MODE_ALLOWED == getAppOpsService().noteOperation(
+                OP_INTERACT_ACROSS_PROFILES, callingUid, callingPackage, /* featureId= */ null,
+                /*shouldCollectAsyncNotedOp= */false, /*message= */null);
+    }
+
+    private boolean isCrossProfilePackageWhitelisted(String packageName) {
+        final long ident = mInjector.clearCallingIdentity();
+        try {
+            return mDpmi.getAllCrossProfilePackages().contains(packageName);
+        } finally {
+            mInjector.restoreCallingIdentity(ident);
+        }
+    }
+
     private List<UserHandle> getTargetUserProfilesUnchecked(
             String callingPackage, @UserIdInt int callingUserId) {
         final long ident = mInjector.clearCallingIdentity();
@@ -239,6 +311,19 @@
         mInjector.getAppOpsManager().checkPackage(mInjector.getCallingUid(), callingPackage);
     }
 
+    private static boolean isPermissionGranted(String permission, int uid) {
+        return PackageManager.PERMISSION_GRANTED == ActivityManager.checkComponentPermission(
+                permission, uid, /* owningUid= */-1, /* exported= */ true);
+    }
+
+    private AppOpsService getAppOpsService() {
+        if (mAppOpsService == null) {
+            IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
+            mAppOpsService = (AppOpsService) IAppOpsService.Stub.asInterface(b);
+        }
+        return mAppOpsService;
+    }
+
     private static class InjectorImpl implements Injector {
         private Context mContext;
 
diff --git a/services/core/java/com/android/server/pm/InstallSource.java b/services/core/java/com/android/server/pm/InstallSource.java
index 0541797..6684e3f 100644
--- a/services/core/java/com/android/server/pm/InstallSource.java
+++ b/services/core/java/com/android/server/pm/InstallSource.java
@@ -18,6 +18,8 @@
 
 import android.annotation.Nullable;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.Objects;
 
 /**
@@ -29,16 +31,27 @@
      * An instance of InstallSource representing an absence of knowledge of the source of
      * a package. Used in preference to null.
      */
-    static final InstallSource EMPTY = new InstallSource(null, null, null, false);
+    static final InstallSource EMPTY = new InstallSource(null, null, null, false, false, null);
 
     /** We also memoize this case because it is common - all un-updated system apps. */
-    private static final InstallSource EMPTY_ORPHANED = new InstallSource(null, null, null, true);
+    private static final InstallSource EMPTY_ORPHANED = new InstallSource(
+            null, null, null, true, false, null);
 
-    /** The package that requested the installation, if known. */
+    /**
+     * The package that requested the installation, if known. May not correspond to a currently
+     * installed package if {@link #isInitiatingPackageUninstalled} is true.
+     */
     @Nullable
     final String initiatingPackageName;
 
     /**
+     * The signing details of the initiating package, if known. Always null if
+     * {@link #initiatingPackageName} is null.
+     */
+    @Nullable
+    final PackageSignatures initiatingPackageSignatures;
+
+    /**
      * The package on behalf of which the initiating package requested the installation, if any.
      * For example if a downloaded APK is installed via the Package Installer this could be the
      * app that performed the download. This value is provided by the initiating package and not
@@ -57,76 +70,120 @@
     /** Indicates if the package that was the installerPackageName has been uninstalled. */
     final boolean isOrphaned;
 
+    /**
+     * Indicates if the package in initiatingPackageName has been uninstalled. Always false if
+     * {@link #initiatingPackageName} is null.
+     */
+    final boolean isInitiatingPackageUninstalled;
+
+    static InstallSource create(@Nullable String initiatingPackageName,
+            @Nullable String originatingPackageName, @Nullable String installerPackageName) {
+        return create(initiatingPackageName, originatingPackageName, installerPackageName,
+                false, false);
+    }
+
     static InstallSource create(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            boolean isOrphaned) {
+            boolean isOrphaned, boolean isInitiatingPackageUninstalled) {
         return createInternal(
                 intern(initiatingPackageName),
                 intern(originatingPackageName),
                 intern(installerPackageName),
-                isOrphaned);
+                isOrphaned, isInitiatingPackageUninstalled, null);
     }
 
     private static InstallSource createInternal(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            boolean isOrphaned) {
+            boolean isOrphaned, boolean isInitiatingPackageUninstalled,
+            @Nullable PackageSignatures initiatingPackageSignatures) {
         if (initiatingPackageName == null && originatingPackageName == null
-                && installerPackageName == null) {
+                && installerPackageName == null && initiatingPackageSignatures == null
+                && !isInitiatingPackageUninstalled) {
             return isOrphaned ? EMPTY_ORPHANED : EMPTY;
         }
         return new InstallSource(initiatingPackageName, originatingPackageName,
-                installerPackageName, isOrphaned);
+                installerPackageName, isOrphaned, isInitiatingPackageUninstalled,
+                initiatingPackageSignatures
+        );
     }
 
     private InstallSource(@Nullable String initiatingPackageName,
             @Nullable String originatingPackageName, @Nullable String installerPackageName,
-            boolean isOrphaned) {
+            boolean isOrphaned, boolean isInitiatingPackageUninstalled,
+            @Nullable PackageSignatures initiatingPackageSignatures) {
+        if (initiatingPackageName == null) {
+            Preconditions.checkArgument(initiatingPackageSignatures == null);
+            Preconditions.checkArgument(!isInitiatingPackageUninstalled);
+        }
         this.initiatingPackageName = initiatingPackageName;
         this.originatingPackageName = originatingPackageName;
         this.installerPackageName = installerPackageName;
         this.isOrphaned = isOrphaned;
+        this.isInitiatingPackageUninstalled = isInitiatingPackageUninstalled;
+        this.initiatingPackageSignatures = initiatingPackageSignatures;
     }
 
     /**
-     * Return an InstallSource the same as this one except with the specified installerPackageName.
+     * Return an InstallSource the same as this one except with the specified
+     * {@link #installerPackageName}.
      */
-    InstallSource setInstallerPackage(String installerPackageName) {
+    InstallSource setInstallerPackage(@Nullable String installerPackageName) {
         if (Objects.equals(installerPackageName, this.installerPackageName)) {
             return this;
         }
         return createInternal(initiatingPackageName, originatingPackageName,
-                intern(installerPackageName), isOrphaned);
+                intern(installerPackageName), isOrphaned, isInitiatingPackageUninstalled,
+                initiatingPackageSignatures
+        );
     }
 
     /**
-     * Return an InstallSource the same as this one except with the specified value for isOrphaned.
+     * Return an InstallSource the same as this one except with the specified value for
+     * {@link #isOrphaned}.
      */
     InstallSource setIsOrphaned(boolean isOrphaned) {
         if (isOrphaned == this.isOrphaned) {
             return this;
         }
         return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
-                isOrphaned);
+                isOrphaned, isInitiatingPackageUninstalled, initiatingPackageSignatures);
     }
 
     /**
-     * Return an InstallSource the same as this one except it does not refer to the specified
-     * installer package name (which is being uninstalled).
+     * Return an InstallSource the same as this one except with the specified
+     * {@link #initiatingPackageSignatures}.
      */
-    InstallSource removeInstallerPackage(String packageName) {
+    InstallSource setInitiatingPackageSignatures(@Nullable PackageSignatures signatures) {
+        if (signatures == initiatingPackageSignatures) {
+            return this;
+        }
+        return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
+                isOrphaned, isInitiatingPackageUninstalled, signatures);
+    }
+
+    /**
+     * Return an InstallSource the same as this one updated to reflect that the specified installer
+     * package name has been uninstalled.
+     */
+    InstallSource removeInstallerPackage(@Nullable String packageName) {
         if (packageName == null) {
             return this;
         }
 
         boolean modified = false;
-        String initiatingPackageName = this.initiatingPackageName;
+        boolean isInitiatingPackageUninstalled = this.isInitiatingPackageUninstalled;
         String originatingPackageName = this.originatingPackageName;
         String installerPackageName = this.installerPackageName;
         boolean isOrphaned = this.isOrphaned;
 
-        if (packageName.equals(initiatingPackageName)) {
-            initiatingPackageName = null;
-            modified = true;
+        if (packageName.equals(this.initiatingPackageName)) {
+            if (!isInitiatingPackageUninstalled) {
+                // In this case we deliberately do not clear the package name (and signatures).
+                // We allow an app to retrieve details of its own install initiator even after
+                // it has been uninstalled.
+                isInitiatingPackageUninstalled = true;
+                modified = true;
+            }
         }
         if (packageName.equals(originatingPackageName)) {
             originatingPackageName = null;
@@ -141,8 +198,9 @@
         if (!modified) {
             return this;
         }
+
         return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
-                isOrphaned);
+                isOrphaned, isInitiatingPackageUninstalled, initiatingPackageSignatures);
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index e2dfa12..6331dd4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -188,7 +188,7 @@
         }
     };
 
-    public PackageInstallerService(Context context, PackageManagerService pm, ApexManager am) {
+    public PackageInstallerService(Context context, PackageManagerService pm) {
         mContext = context;
         mPm = pm;
         mPermissionManager = LocalServices.getService(PermissionManagerServiceInternal.class);
@@ -206,9 +206,8 @@
         mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions");
         mSessionsDir.mkdirs();
 
-        mApexManager = am;
-
-        mStagingManager = new StagingManager(this, am, context);
+        mApexManager = ApexManager.getInstance();
+        mStagingManager = new StagingManager(this, context);
     }
 
     boolean okToSendBroadcasts()  {
@@ -635,7 +634,7 @@
             }
         }
         InstallSource installSource = InstallSource.create(installerPackageName,
-                originatingPackageName, requestedInstallerPackageName, false);
+                originatingPackageName, requestedInstallerPackageName);
         session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
                 mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid,
                 installSource, params, createdMillis,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 426cd01..165bdeb 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -140,6 +140,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
@@ -209,6 +210,8 @@
     private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
     private static final int[] EMPTY_CHILD_SESSION_ARRAY = {};
 
+    private static final String SYSTEM_DATA_LOADER_PACKAGE = "android";
+
     // TODO: enforce INSTALL_ALLOW_TEST
     // TODO: enforce INSTALL_ALLOW_DOWNGRADE
 
@@ -555,6 +558,12 @@
                                 params.dataLoaderParams);
             }
         }
+
+        if (isStreamingInstallation()
+                && this.params.dataLoaderParams.getComponentName().getPackageName()
+                == SYSTEM_DATA_LOADER_PACKAGE) {
+            assertShellOrSystemCalling("System data loaders");
+        }
     }
 
     public SessionInfo generateInfo() {
@@ -770,6 +779,17 @@
         }
     }
 
+    private void assertShellOrSystemCalling(String operation) {
+        switch (Binder.getCallingUid()) {
+            case android.os.Process.SHELL_UID:
+            case android.os.Process.ROOT_UID:
+            case android.os.Process.SYSTEM_UID:
+                break;
+            default:
+                throw new SecurityException(operation + " only supported from shell or system");
+        }
+    }
+
     private void assertCanWrite(boolean reverseMode) {
         if (isDataLoaderInstallation()) {
             throw new IllegalStateException(
@@ -780,15 +800,7 @@
             assertPreparedAndNotSealedLocked("assertCanWrite");
         }
         if (reverseMode) {
-            switch (Binder.getCallingUid()) {
-                case android.os.Process.SHELL_UID:
-                case android.os.Process.ROOT_UID:
-                case android.os.Process.SYSTEM_UID:
-                    break;
-                default:
-                    throw new SecurityException(
-                            "Reverse mode only supported from shell or system");
-            }
+            assertShellOrSystemCalling("Reverse mode");
         }
     }
 
@@ -1025,13 +1037,24 @@
         mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
     }
 
-    private class FileSystemConnector extends IPackageInstallerSessionFileSystemConnector.Stub {
+    private final class FileSystemConnector extends
+            IPackageInstallerSessionFileSystemConnector.Stub {
+        final Set<String> mAddedFiles;
+
+        FileSystemConnector(List<InstallationFile> addedFiles) {
+            mAddedFiles = addedFiles.stream().map(file -> file.getName()).collect(
+                    Collectors.toSet());
+        }
+
         @Override
         public void writeData(String name, long offsetBytes, long lengthBytes,
                 ParcelFileDescriptor incomingFd) {
             if (incomingFd == null) {
                 throw new IllegalArgumentException("incomingFd can't be null");
             }
+            if (!mAddedFiles.contains(name)) {
+                throw new SecurityException("File name is not in the list of added files.");
+            }
             try {
                 doWriteInternal(name, offsetBytes, lengthBytes, incomingFd);
             } catch (IOException e) {
@@ -1452,7 +1475,7 @@
                 }
 
                 mInstallerUid = uid;
-                mInstallSource = InstallSource.create(packageName, null, packageName, false);
+                mInstallSource = InstallSource.create(packageName, null, packageName);
             }
         } catch (PackageManager.NameNotFoundException e) {
             onSessionTransferStatus(statusReceiver, packageName,
@@ -2390,16 +2413,6 @@
 
     @Override
     public void addFile(String name, long lengthBytes, byte[] metadata) {
-        if (mIncrementalFileStorages != null) {
-            try {
-                mIncrementalFileStorages.addFile(new InstallationFile(name, lengthBytes, metadata));
-                //TODO(b/136132412): merge incremental and callback installation schemes
-                return;
-            } catch (IOException ex) {
-                throw new IllegalStateException(
-                        "Failed to add and configure Incremental File: " + name, ex);
-            }
-        }
         if (!isDataLoaderInstallation()) {
             throw new IllegalStateException(
                     "Cannot add files to non-data loader installation session.");
@@ -2412,7 +2425,6 @@
         synchronized (mLock) {
             assertCallerIsOwnerOrRootLocked();
             assertPreparedAndNotSealedLocked("addFile");
-
             mFiles.add(FileInfo.added(name, lengthBytes, metadata));
         }
     }
@@ -2463,12 +2475,10 @@
      */
     private void prepareDataLoader()
             throws PackageManagerException, StreamingException {
-        if (!isStreamingInstallation()) {
+        if (!isDataLoaderInstallation()) {
             return;
         }
 
-        FileSystemConnector connector = new FileSystemConnector();
-
         List<InstallationFile> addedFiles = mFiles.stream().filter(
                 file -> sAddedFilter.accept(new File(file.name))).map(
                     file -> new InstallationFile(
@@ -2479,6 +2489,20 @@
                     file -> file.name.substring(
                             0, file.name.length() - REMOVE_MARKER_EXTENSION.length())).collect(
                 Collectors.toList());
+        if (mIncrementalFileStorages != null) {
+            for (InstallationFile file : addedFiles) {
+                try {
+                    mIncrementalFileStorages.addFile(file);
+                } catch (IOException ex) {
+                    // TODO(b/146080380): add incremental-specific error code
+                    throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                            "Failed to add and configure Incremental File: " + file.getName(), ex);
+                }
+            }
+            return;
+        }
+
+        final FileSystemConnector connector = new FileSystemConnector(addedFiles);
 
         DataLoaderManager dataLoaderManager = mContext.getSystemService(DataLoaderManager.class);
         if (dataLoaderManager == null) {
@@ -2515,11 +2539,11 @@
             }
         };
 
-        final DataLoaderParams params = this.params.dataLoaderParams;
-
         final FileSystemControlParcel control = new FileSystemControlParcel();
         control.callback = connector;
 
+        final DataLoaderParams params = this.params.dataLoaderParams;
+
         Bundle dataLoaderParams = new Bundle();
         dataLoaderParams.putParcelable("componentName", params.getComponentName());
         dataLoaderParams.putParcelable("control", control);
@@ -3115,7 +3139,7 @@
         }
 
         InstallSource installSource = InstallSource.create(installInitiatingPackageName,
-                installOriginatingPackageName, installerPackageName, false);
+                installOriginatingPackageName, installerPackageName);
         return new PackageInstallerSession(callback, context, pm, sessionProvider,
                 installerThread, stagingManager, sessionId, userId, installerUid,
                 installSource, params, createdMillis, stageDir, stageCid, fileInfosArray,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6bd9c48..dad32cd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -492,6 +492,7 @@
     static final int SCAN_AS_PRODUCT = 1 << 20;
     static final int SCAN_AS_SYSTEM_EXT = 1 << 21;
     static final int SCAN_AS_ODM = 1 << 22;
+    static final int SCAN_AS_APK_IN_APEX = 1 << 23;
 
     @IntDef(flag = true, prefix = { "SCAN_" }, value = {
             SCAN_NO_DEX,
@@ -2589,6 +2590,9 @@
                     & (SCAN_AS_VENDOR | SCAN_AS_ODM | SCAN_AS_PRODUCT | SCAN_AS_SYSTEM_EXT)) != 0) {
                 return true;
             }
+            if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
+                return true;
+            }
             return false;
         }
 
@@ -3332,7 +3336,7 @@
                 }
             }
 
-            mInstallerService = new PackageInstallerService(mContext, this, mApexManager);
+            mInstallerService = new PackageInstallerService(mContext, this);
             final Pair<ComponentName, String> instantAppResolverComponent =
                     getInstantAppResolverLPr();
             if (instantAppResolverComponent != null) {
@@ -5344,8 +5348,9 @@
         if (!mUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForComponent(flags, userId);
-        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
-                false /* requireFullPermission */, false /* checkShell */, "get service info");
+        mPermissionManager.enforceCrossUserOrProfilePermission(
+                callingUid, userId, false /* requireFullPermission */, false /* checkShell */,
+                "get service info");
         synchronized (mLock) {
             ParsedService s = mComponentResolver.getService(component);
             if (DEBUG_PACKAGE_INFO) Log.v(
@@ -7795,8 +7800,10 @@
             String resolvedType, int flags, int userId, int callingUid,
             boolean includeInstantApps) {
         if (!mUserManager.exists(userId)) return Collections.emptyList();
-        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
-                false /*requireFullPermission*/, false /*checkShell*/,
+        mPermissionManager.enforceCrossUserOrProfilePermission(callingUid,
+                userId,
+                false /*requireFullPermission*/,
+                false /*checkShell*/,
                 "query intent receivers");
         final String instantAppPkgName = getInstantAppPackageName(callingUid);
         flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps);
@@ -11595,6 +11602,23 @@
             return false;
         }
         SharedLibraryInfo libraryInfo = versionedLib.valueAt(libIdx);
+
+        // Remove the shared library overlays from its dependent packages.
+        for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
+            final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr(
+                    libraryInfo, 0, currentUserId);
+            if (dependents == null) {
+                continue;
+            }
+            for (VersionedPackage dependentPackage : dependents) {
+                final PackageSetting ps = mSettings.mPackages.get(
+                        dependentPackage.getPackageName());
+                if (ps != null) {
+                    ps.setOverlayPathsForLibrary(libraryInfo.getName(), null, currentUserId);
+                }
+            }
+        }
+
         versionedLib.remove(version);
         if (versionedLib.size() <= 0) {
             mSharedLibraries.remove(name);
@@ -11710,6 +11734,9 @@
             mSettings.insertPackageSettingLPw(pkgSetting, pkg);
             // Add the new setting to mPackages
             mPackages.put(pkg.getAppInfoPackageName(), pkg);
+            if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
+                mApexManager.registerApkInApex(pkg);
+            }
 
             // Add the package's KeySets to the global KeySetManagerService
             KeySetManagerService ksms = mSettings.mKeySetManagerService;
@@ -15158,10 +15185,10 @@
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
 
         final String pkgName = pkg.getPackageName();
-        final InstallSource installSource = installArgs.installSource;
-        final String installerPackageName = installSource.installerPackageName;
         final int[] installedForUsers = res.origUsers;
         final int installReason = installArgs.installReason;
+        InstallSource installSource = installArgs.installSource;
+        final String installerPackageName = installSource.installerPackageName;
 
         if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getCodePath());
         synchronized (mLock) {
@@ -15200,6 +15227,29 @@
                         // upcoming call to mSettings.writeLPr().
                     }
                 }
+
+                // Retrieve the overlays for shared libraries of the package.
+                if (pkg.getUsesLibraryInfos() != null) {
+                    for (SharedLibraryInfo sharedLib : pkg.getUsesLibraryInfos()) {
+                        for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
+                            if (!sharedLib.isDynamic()) {
+                                // TODO(146804378): Support overlaying static shared libraries
+                                continue;
+                            }
+                            final PackageSetting libPs = mSettings.mPackages.get(
+                                    sharedLib.getPackageName());
+                            if (libPs == null) {
+                                continue;
+                            }
+                            final String[] overlayPaths = libPs.getOverlayPaths(currentUserId);
+                            if (overlayPaths != null) {
+                                ps.setOverlayPathsForLibrary(sharedLib.getName(),
+                                        Arrays.asList(overlayPaths), currentUserId);
+                            }
+                        }
+                    }
+                }
+
                 // It's implied that when a user requests installation, they want the app to be
                 // installed and enabled.
                 if (userId != UserHandle.USER_ALL) {
@@ -15207,6 +15257,14 @@
                     ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
                 }
 
+                if (installSource.initiatingPackageName != null) {
+                    final PackageSetting ips = mSettings.mPackages.get(
+                            installSource.initiatingPackageName);
+                    if (ips != null) {
+                        installSource = installSource.setInitiatingPackageSignatures(
+                                ips.signatures);
+                    }
+                }
                 ps.setInstallSource(installSource);
                 mSettings.addInstallerPackageNames(installSource);
 
@@ -17749,10 +17807,10 @@
             ApexManager.ActiveApexInfo apexInfo) {
         for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
             SystemPartition sp = SYSTEM_PARTITIONS.get(i);
-            if (apexInfo.preinstalledApexPath.getAbsolutePath().startsWith(
+            if (apexInfo.preInstalledApexPath.getAbsolutePath().startsWith(
                     sp.folder.getAbsolutePath())) {
-                return new SystemPartition(apexInfo.apexDirectory, sp.scanFlag,
-                        false /* hasOverlays */);
+                return new SystemPartition(apexInfo.apexDirectory,
+                        sp.scanFlag | SCAN_AS_APK_IN_APEX, false /* hasOverlays */);
             }
         }
         return null;
@@ -19372,7 +19430,7 @@
             // PermissionController manages default home directly.
             return false;
         }
-        mPermissionManager.setDefaultHome(currentPackageName, userId, (successful) -> {
+        mPermissionManager.setDefaultHome(packageName, userId, (successful) -> {
             if (successful) {
                 postPreferredActivityChangedBroadcast(userId);
             }
@@ -19976,19 +20034,25 @@
                 }
             }
 
-            // All installSource strings are interned, so == is ok here
-            if (installSource.initiatingPackageName == installSource.installerPackageName) {
-                // The installer and initiator will often be the same, and when they are
-                // we can skip doing the same check again.
-                initiatingPackageName = installerPackageName;
+            if (installSource.isInitiatingPackageUninstalled) {
+                // TODO(b/146555198) Allow the app itself to see the info
+                // (at least for non-instant apps)
+                initiatingPackageName = null;
             } else {
-                initiatingPackageName = installSource.initiatingPackageName;
-                final PackageSetting ps = mSettings.mPackages.get(initiatingPackageName);
-                if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) {
-                    initiatingPackageName = null;
+                // All installSource strings are interned, so == is ok here
+                if (installSource.initiatingPackageName == installSource.installerPackageName) {
+                    // The installer and initiator will often be the same, and when they are
+                    // we can skip doing the same check again.
+                    initiatingPackageName = installerPackageName;
+                } else {
+                    initiatingPackageName = installSource.initiatingPackageName;
+                    final PackageSetting ps = mSettings.mPackages.get(initiatingPackageName);
+                    if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) {
+                        initiatingPackageName = null;
+                    }
                 }
-
             }
+
             originatingPackageName = installSource.originatingPackageName;
             if (originatingPackageName != null) {
                 final PackageSetting ps = mSettings.mPackages.get(originatingPackageName);
@@ -20108,8 +20172,7 @@
         // Disable any carrier apps. We do this very early in boot to prevent the apps from being
         // disabled after already being started.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), this,
-                mPermissionManagerService, mContext.getContentResolver(),
-                UserHandle.USER_SYSTEM);
+                mPermissionManagerService, UserHandle.USER_SYSTEM, mContext);
 
         disableSkuSpecificApps();
 
@@ -22777,7 +22840,7 @@
             ArrayList<String> systemPackageNames = new ArrayList<>(pkgNames.length);
 
             for (String pkgName: pkgNames) {
-                synchronized (mPackages) {
+                synchronized (mLock) {
                     if (pkgName == null) {
                         continue;
                     }
@@ -23226,9 +23289,11 @@
 
         @Override
         public boolean setEnabledOverlayPackages(int userId, @NonNull String targetPackageName,
-                @Nullable List<String> overlayPackageNames) {
+                @Nullable List<String> overlayPackageNames,
+                @NonNull Collection<String> outUpdatedPackageNames) {
             synchronized (mLock) {
-                if (targetPackageName == null || mPackages.get(targetPackageName) == null) {
+                final AndroidPackage targetPkg = mPackages.get(targetPackageName);
+                if (targetPackageName == null || targetPkg == null) {
                     Slog.e(TAG, "failed to find package " + targetPackageName);
                     return false;
                 }
@@ -23247,8 +23312,41 @@
                     }
                 }
 
+                ArraySet<String> updatedPackageNames = null;
+                if (targetPkg.getLibraryNames() != null) {
+                    // Set the overlay paths for dependencies of the shared library.
+                    updatedPackageNames = new ArraySet<>();
+                    for (String libName : targetPkg.getLibraryNames()) {
+                        final SharedLibraryInfo info = getSharedLibraryInfoLPr(libName,
+                                SharedLibraryInfo.VERSION_UNDEFINED);
+                        if (info == null) {
+                            continue;
+                        }
+                        final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr(
+                                info, 0, userId);
+                        if (dependents == null) {
+                            continue;
+                        }
+                        for (VersionedPackage dependent : dependents) {
+                            final PackageSetting ps = mSettings.mPackages.get(
+                                    dependent.getPackageName());
+                            if (ps == null) {
+                                continue;
+                            }
+                            ps.setOverlayPathsForLibrary(libName, overlayPaths, userId);
+                            updatedPackageNames.add(dependent.getPackageName());
+                        }
+                    }
+                }
+
                 final PackageSetting ps = mSettings.mPackages.get(targetPackageName);
                 ps.setOverlayPaths(overlayPaths, userId);
+
+                outUpdatedPackageNames.add(targetPackageName);
+                if (updatedPackageNames != null) {
+                    outUpdatedPackageNames.addAll(updatedPackageNames);
+                }
+
                 return true;
             }
         }
@@ -23438,6 +23536,11 @@
         }
 
         @Override
+        public List<String> getApksInApex(String apexPackageName) {
+            return PackageManagerService.this.mApexManager.getApksInApex(apexPackageName);
+        }
+
+        @Override
         public void uninstallApex(String packageName, long versionCode, int userId,
                 IntentSender intentSender, int flags) {
             final int callerUid = Binder.getCallingUid();
@@ -23618,7 +23721,7 @@
 
     @Nullable
     public PackageSetting getPackageSetting(String packageName) {
-        synchronized (mPackages) {
+        synchronized (mLock) {
             packageName = resolveInternalPackageNameLPr(
                     packageName, PackageManager.VERSION_CODE_HIGHEST);
             return mSettings.mPackages.get(packageName);
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 10e2780..5adab37 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -34,7 +34,6 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.DataLoaderParams;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageInstaller;
@@ -137,10 +136,6 @@
     private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/";
     private static final int DEFAULT_WAIT_MS = 60 * 1000;
 
-    private static final String DATA_LOADER_PACKAGE = "android";
-    private static final String DATA_LOADER_CLASS =
-            "com.android.server.pm.PackageManagerShellCommandDataLoader";
-
     final IPackageManager mInterface;
     final IPermissionManager mPermissionManager;
     final private WeakHashMap<String, Resources> mResourceCache =
@@ -164,7 +159,7 @@
 
         final PrintWriter pw = getOutPrintWriter();
         try {
-            switch(cmd) {
+            switch (cmd) {
                 case "path":
                     return runPath();
                 case "dump":
@@ -1163,9 +1158,8 @@
     private int runStreamingInstall() throws RemoteException {
         final InstallParams params = makeInstallParams();
         if (params.sessionParams.dataLoaderParams == null) {
-            final DataLoaderParams dataLoaderParams = DataLoaderParams.forStreaming(
-                    new ComponentName(DATA_LOADER_PACKAGE, DATA_LOADER_CLASS), "");
-            params.sessionParams.setDataLoaderParams(dataLoaderParams);
+            params.sessionParams.setDataLoaderParams(
+                    PackageManagerShellCommandDataLoader.getDataLoaderParams(this));
         }
         return doRunInstall(params);
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
index 1ee9ab8..a814cb8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
@@ -17,18 +17,22 @@
 package com.android.server.pm;
 
 import android.annotation.NonNull;
+import android.content.ComponentName;
 import android.content.pm.DataLoaderParams;
 import android.content.pm.InstallationFile;
 import android.os.ParcelFileDescriptor;
+import android.os.ShellCommand;
 import android.service.dataloader.DataLoaderService;
 import android.text.TextUtils;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import libcore.io.IoUtils;
 
-import java.io.File;
 import java.io.IOException;
+import java.lang.ref.WeakReference;
 import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
 import java.util.Collection;
 
 /**
@@ -37,41 +41,109 @@
 public class PackageManagerShellCommandDataLoader extends DataLoaderService {
     public static final String TAG = "PackageManagerShellCommandDataLoader";
 
-    static class DataLoader implements DataLoaderService.DataLoader {
-        private ParcelFileDescriptor mInFd = null;
-        private FileSystemConnector mConnector = null;
+    private static final String PACKAGE = "android";
+    private static final String CLASS = PackageManagerShellCommandDataLoader.class.getName();
 
-        private static final String STDIN_PATH = "-";
+    static final SecureRandom sRandom = new SecureRandom();
+    static final SparseArray<WeakReference<ShellCommand>> sShellCommands = new SparseArray<>();
+
+    private static final char ARGS_DELIM = '&';
+    private static final String SHELL_COMMAND_ID_PREFIX = "shellCommandId=";
+    private static final int INVALID_SHELL_COMMAND_ID = -1;
+    private static final int TOO_MANY_PENDING_SHELL_COMMANDS = 10;
+
+    private static final String STDIN_PATH = "-";
+
+    static DataLoaderParams getDataLoaderParams(ShellCommand shellCommand) {
+        int commandId;
+        synchronized (sShellCommands) {
+            // Clean up old references.
+            for (int i = sShellCommands.size() - 1; i >= 0; i--) {
+                WeakReference<ShellCommand> oldRef = sShellCommands.valueAt(i);
+                if (oldRef.get() == null) {
+                    sShellCommands.removeAt(i);
+                }
+            }
+
+            // Sanity check.
+            if (sShellCommands.size() > TOO_MANY_PENDING_SHELL_COMMANDS) {
+                Slog.e(TAG, "Too many pending shell commands: " + sShellCommands.size());
+            }
+
+            // Generate new id and put ref to the array.
+            do {
+                commandId = sRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
+            } while (sShellCommands.contains(commandId));
+
+            sShellCommands.put(commandId, new WeakReference<>(shellCommand));
+        }
+
+        final String args = SHELL_COMMAND_ID_PREFIX + commandId;
+        return DataLoaderParams.forStreaming(new ComponentName(PACKAGE, CLASS), args);
+    }
+
+    private static int extractShellCommandId(String args) {
+        int sessionIdIdx = args.indexOf(SHELL_COMMAND_ID_PREFIX);
+        if (sessionIdIdx < 0) {
+            Slog.e(TAG, "Missing shell command id param.");
+            return INVALID_SHELL_COMMAND_ID;
+        }
+        sessionIdIdx += SHELL_COMMAND_ID_PREFIX.length();
+        int delimIdx = args.indexOf(ARGS_DELIM, sessionIdIdx);
+        try {
+            if (delimIdx < 0) {
+                return Integer.parseInt(args.substring(sessionIdIdx));
+            } else {
+                return Integer.parseInt(args.substring(sessionIdIdx, delimIdx));
+            }
+        } catch (NumberFormatException e) {
+            Slog.e(TAG, "Incorrect shell command id format.", e);
+            return INVALID_SHELL_COMMAND_ID;
+        }
+    }
+
+    static class DataLoader implements DataLoaderService.DataLoader {
+        private DataLoaderParams mParams = null;
+        private FileSystemConnector mConnector = null;
 
         @Override
         public boolean onCreate(@NonNull DataLoaderParams dataLoaderParams,
                 @NonNull FileSystemConnector connector) {
+            mParams = dataLoaderParams;
             mConnector = connector;
             return true;
         }
+
         @Override
         public boolean onPrepareImage(Collection<InstallationFile> addedFiles,
                 Collection<String> removedFiles) {
+            final int commandId = extractShellCommandId(mParams.getArguments());
+            if (commandId == INVALID_SHELL_COMMAND_ID) {
+                return false;
+            }
+
+            final WeakReference<ShellCommand> shellCommandRef;
+            synchronized (sShellCommands) {
+                shellCommandRef = sShellCommands.get(commandId, null);
+            }
+            final ShellCommand shellCommand =
+                    shellCommandRef != null ? shellCommandRef.get() : null;
+            if (shellCommand == null) {
+                Slog.e(TAG, "Missing shell command.");
+                return false;
+            }
             try {
                 for (InstallationFile fileInfo : addedFiles) {
                     String filePath = new String(fileInfo.getMetadata(), StandardCharsets.UTF_8);
                     if (STDIN_PATH.equals(filePath) || TextUtils.isEmpty(filePath)) {
-                        // TODO(b/146080380): add support for STDIN installations.
-                        if (mInFd == null) {
-                            Slog.e(TAG, "Invalid stdin file descriptor.");
-                            return false;
-                        }
-                        ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(
-                                mInFd.getFileDescriptor());
+                        final ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(
+                                shellCommand.getInFileDescriptor());
                         mConnector.writeData(fileInfo.getName(), 0, fileInfo.getSize(), inFd);
                     } else {
-                        File localFile = new File(filePath);
                         ParcelFileDescriptor incomingFd = null;
                         try {
-                            // TODO(b/146080380): open files via callback into shell command.
-                            incomingFd = ParcelFileDescriptor.open(localFile,
-                                    ParcelFileDescriptor.MODE_READ_ONLY);
-                            mConnector.writeData(fileInfo.getName(), 0, localFile.length(),
+                            incomingFd = shellCommand.openFileForSystem(filePath, "r");
+                            mConnector.writeData(fileInfo.getName(), 0, incomingFd.getStatSize(),
                                     incomingFd);
                         } finally {
                             IoUtils.closeQuietly(incomingFd);
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 0c0b93b..f1ac0af 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -41,6 +41,7 @@
 import java.io.File;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -312,12 +313,21 @@
     }
 
     void setOverlayPaths(List<String> overlayPaths, int userId) {
-        modifyUserState(userId).overlayPaths = overlayPaths == null ? null :
-            overlayPaths.toArray(new String[overlayPaths.size()]);
+        modifyUserState(userId).setOverlayPaths(overlayPaths == null ? null :
+            overlayPaths.toArray(new String[overlayPaths.size()]));
     }
 
     String[] getOverlayPaths(int userId) {
-        return readUserState(userId).overlayPaths;
+        return readUserState(userId).getOverlayPaths();
+    }
+
+    void setOverlayPathsForLibrary(String libName, List<String> overlayPaths, int userId) {
+        modifyUserState(userId).setSharedLibraryOverlayPaths(libName,
+                overlayPaths == null ? null : overlayPaths.toArray(new String[0]));
+    }
+
+    Map<String, String[]> getOverlayPathsForLibrary(int userId) {
+        return readUserState(userId).getSharedLibraryOverlayPaths();
     }
 
     /** Only use for testing. Do NOT use in production code. */
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f9a3361..3e665c3 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2819,6 +2819,9 @@
         if (installSource.initiatingPackageName != null) {
             serializer.attribute(null, "installInitiator", installSource.initiatingPackageName);
         }
+        if (installSource.isInitiatingPackageUninstalled) {
+            serializer.attribute(null, "installInitiatorUninstalled", "true");
+        }
         if (installSource.originatingPackageName != null) {
             serializer.attribute(null, "installOriginator", installSource.originatingPackageName);
         }
@@ -2836,6 +2839,11 @@
 
         pkg.signatures.writeXml(serializer, "sigs", mPastSignatures);
 
+        if (installSource.initiatingPackageSignatures != null) {
+            installSource.initiatingPackageSignatures.writeXml(
+                    serializer, "install-initiator-sigs", mPastSignatures);
+        }
+
         writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissionStates());
 
         writeSigningKeySetLPr(serializer, pkg.keySetData);
@@ -3571,6 +3579,7 @@
         String isOrphaned = null;
         String installOriginatingPackageName = null;
         String installInitiatingPackageName = null;
+        String installInitiatorUninstalled = null;
         String volumeUuid = null;
         String categoryHintString = null;
         String updateAvailable = null;
@@ -3616,6 +3625,8 @@
             isOrphaned = parser.getAttributeValue(null, "isOrphaned");
             installInitiatingPackageName = parser.getAttributeValue(null, "installInitiator");
             installOriginatingPackageName = parser.getAttributeValue(null, "installOriginator");
+            installInitiatorUninstalled = parser.getAttributeValue(null,
+                    "installInitiatorUninstalled");
             volumeUuid = parser.getAttributeValue(null, "volumeUuid");
             categoryHintString = parser.getAttributeValue(null, "categoryHint");
             if (categoryHintString != null) {
@@ -3772,7 +3783,8 @@
             packageSetting.uidError = "true".equals(uidError);
             InstallSource installSource = InstallSource.create(
                     installInitiatingPackageName, installOriginatingPackageName,
-                    installerPackageName, "true".equals(isOrphaned));
+                    installerPackageName, "true".equals(isOrphaned),
+                    "true".equals(installInitiatorUninstalled));
             packageSetting.installSource = installSource;
             packageSetting.volumeUuid = volumeUuid;
             packageSetting.categoryHint = categoryHint;
@@ -3849,6 +3861,11 @@
                         mKeySetRefs.put(id, 1);
                     }
                     packageSetting.keySetData.addDefinedKeySet(id, alias);
+                } else if (tagName.equals("install-initiator-sigs")) {
+                    final PackageSignatures signatures = new PackageSignatures();
+                    signatures.readXml(parser, mPastSignatures);
+                    packageSetting.installSource =
+                            packageSetting.installSource.setInitiatingPackageSignatures(signatures);
                 } else if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
                     readDomainVerificationLPw(parser, packageSetting);
                 } else {
@@ -4725,6 +4742,22 @@
                 }
             }
 
+            Map<String, String[]> sharedLibraryOverlayPaths =
+                    ps.getOverlayPathsForLibrary(user.id);
+            if (sharedLibraryOverlayPaths != null) {
+                for (Map.Entry<String, String[]> libOverlayPaths :
+                        sharedLibraryOverlayPaths.entrySet()) {
+                    if (libOverlayPaths.getValue() == null) {
+                        continue;
+                    }
+                    pw.print(prefix); pw.print("  ");
+                    pw.print(libOverlayPaths.getKey()); pw.println(" overlay paths:");
+                    for (String path : libOverlayPaths.getValue()) {
+                        pw.print(prefix); pw.print("    "); pw.println(path);
+                    }
+                }
+            }
+
             String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
             if (lastDisabledAppCaller != null) {
                 pw.print(prefix); pw.print("    lastDisabledCaller: ");
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 9e462cd..2265d01 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -33,11 +33,13 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.PackageParser.SigningDetails;
 import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
 import android.content.pm.ParceledListSlice;
+import android.content.pm.parsing.AndroidPackage;
 import android.content.rollback.IRollbackManager;
 import android.content.rollback.RollbackInfo;
 import android.content.rollback.RollbackManager;
@@ -50,6 +52,8 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManagerInternal;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
 import android.util.IntArray;
@@ -61,6 +65,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.PackageHelper;
 import com.android.internal.os.BackgroundThread;
+import com.android.server.LocalServices;
 
 import java.io.File;
 import java.io.IOException;
@@ -93,10 +98,11 @@
     @GuardedBy("mStagedSessions")
     private final SparseIntArray mSessionRollbackIds = new SparseIntArray();
 
-    StagingManager(PackageInstallerService pi, ApexManager am, Context context) {
+    StagingManager(PackageInstallerService pi, Context context) {
         mPi = pi;
-        mApexManager = am;
         mContext = context;
+
+        mApexManager = ApexManager.getInstance();
         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
         mPreRebootVerificationHandler = new PreRebootVerificationHandler(
                 BackgroundThread.get().getLooper());
@@ -334,6 +340,88 @@
         return PackageHelper.getStorageManager().needsCheckpoint();
     }
 
+    /**
+     * Apks inside apex are not installed using apk-install flow. They are scanned from the system
+     * directory directly by PackageManager, as such, RollbackManager need to handle their data
+     * separately here.
+     */
+    private void snapshotAndRestoreApkInApexUserData(PackageInstallerSession session) {
+        // We want to process apks inside apex. So current session needs to contain apex.
+        if (!sessionContainsApex(session)) {
+            return;
+        }
+
+        boolean doSnapshotOrRestore =
+                (session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
+                || session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
+        if (!doSnapshotOrRestore) {
+            return;
+        }
+
+        // Find all the apex sessions that needs processing
+        List<PackageInstallerSession> apexSessions = new ArrayList<>();
+        if (session.isMultiPackage()) {
+            List<PackageInstallerSession> childrenSessions = new ArrayList<>();
+            synchronized (mStagedSessions) {
+                for (int childSessionId : session.getChildSessionIds()) {
+                    PackageInstallerSession childSession = mStagedSessions.get(childSessionId);
+                    if (childSession != null) {
+                        childrenSessions.add(childSession);
+                    }
+                }
+            }
+            for (PackageInstallerSession childSession : childrenSessions) {
+                if (sessionContainsApex(childSession)) {
+                    apexSessions.add(childSession);
+                }
+            }
+        } else {
+            apexSessions.add(session);
+        }
+
+        // For each apex, process the apks inside it
+        for (PackageInstallerSession apexSession : apexSessions) {
+            List<String> apksInApex = mApexManager.getApksInApex(apexSession.getPackageName());
+            for (String apk: apksInApex) {
+                snapshotAndRestoreApkInApexUserData(apk);
+            }
+        }
+    }
+
+    private void snapshotAndRestoreApkInApexUserData(String packageName) {
+        IRollbackManager rm = IRollbackManager.Stub.asInterface(
+                    ServiceManager.getService(Context.ROLLBACK_SERVICE));
+
+        PackageManagerInternal mPmi = LocalServices.getService(PackageManagerInternal.class);
+        AndroidPackage pkg = mPmi.getPackage(packageName);
+        if (pkg == null) {
+            Slog.e(TAG, "Could not find package: " + packageName
+                    + "for snapshotting/restoring user data.");
+            return;
+        }
+        final String seInfo = pkg.getSeInfo();
+        final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
+        final int[] allUsers = um.getUserIds();
+
+        int appId = -1;
+        long ceDataInode = -1;
+        final PackageSetting ps = (PackageSetting) mPmi.getPackageSetting(packageName);
+        if (ps != null && rm != null) {
+            appId = ps.appId;
+            ceDataInode = ps.getCeDataInode(UserHandle.USER_SYSTEM);
+            // NOTE: We ignore the user specified in the InstallParam because we know this is
+            // an update, and hence need to restore data for all installed users.
+            final int[] installedUsers = ps.queryInstalledUsers(allUsers, true);
+
+            try {
+                rm.snapshotAndRestoreUserData(packageName, installedUsers, appId, ceDataInode,
+                        seInfo, 0 /*token*/);
+            } catch (RemoteException re) {
+                Slog.e(TAG, "Error snapshotting/restoring user data: " + re);
+            }
+        }
+    }
+
     private void resumeSession(@NonNull PackageInstallerSession session) {
         Slog.d(TAG, "Resuming session " + session.sessionId);
 
@@ -407,6 +495,7 @@
                 abortCheckpoint();
                 return;
             }
+            snapshotAndRestoreApkInApexUserData(session);
             Slog.i(TAG, "APEX packages in session " + session.sessionId
                     + " were successfully activated. Proceeding with APK packages, if any");
         }
@@ -529,7 +618,7 @@
                         Arrays.stream(session.getChildSessionIds())
                                 // Retrieve cached sessions matching ids.
                                 .mapToObj(i -> mStagedSessions.get(i))
-                                // Filter only the ones containing APKs.s
+                                // Filter only the ones containing APKs.
                                 .filter(childSession -> !isApexSession(childSession))
                                 .collect(Collectors.toList());
             }
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 6d6ec25..46893b2 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -592,12 +592,6 @@
                 getDefaultSystemHandlerActivityPackageForCategory(Intent.CATEGORY_APP_MAPS, userId),
                 userId, ALWAYS_LOCATION_PERMISSIONS);
 
-        // Gallery
-        grantPermissionsToSystemPackage(
-                getDefaultSystemHandlerActivityPackageForCategory(
-                        Intent.CATEGORY_APP_GALLERY, userId),
-                userId, STORAGE_PERMISSIONS);
-
         // Email
         grantPermissionsToSystemPackage(
                 getDefaultSystemHandlerActivityPackageForCategory(
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index d921f31..d468cd9 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -4005,21 +4005,128 @@
             PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt,
                     UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
         }
-        if (!requirePermissionWhenSameUser && userId == UserHandle.getUserId(callingUid)) return;
-        if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) {
-            if (requireFullPermission) {
-                mContext.enforceCallingOrSelfPermission(
-                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
-            } else {
-                try {
-                    mContext.enforceCallingOrSelfPermission(
-                            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
-                } catch (SecurityException se) {
-                    mContext.enforceCallingOrSelfPermission(
-                            android.Manifest.permission.INTERACT_ACROSS_USERS, message);
-                }
-            }
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (hasCrossUserPermission(
+                callingUid, callingUserId, userId, requireFullPermission,
+                requirePermissionWhenSameUser)) {
+            return;
         }
+        String errorMessage = buildInvalidCrossUserPermissionMessage(
+                message, requireFullPermission);
+        Slog.w(TAG, errorMessage);
+        throw new SecurityException(errorMessage);
+    }
+
+    /**
+     * Checks if the request is from the system or an app that has the appropriate cross-user
+     * permissions defined as follows:
+     * <ul>
+     * <li>INTERACT_ACROSS_USERS_FULL if {@code requireFullPermission} is true.</li>
+     * <li>INTERACT_ACROSS_USERS if the given {@userId} is in a different profile group
+     * to the caller.</li>
+     * <li>Otherwise, INTERACT_ACROSS_PROFILES if the given {@userId} is in the same profile group
+     * as the caller.</li>
+     * </ul>
+     *
+     * @param checkShell whether to prevent shell from access if there's a debugging restriction
+     * @param message the message to log on security exception
+     */
+    private void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+            boolean requireFullPermission, boolean checkShell,
+            String message) {
+        if (userId < 0) {
+            throw new IllegalArgumentException("Invalid userId " + userId);
+        }
+        if (checkShell) {
+            PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt,
+                    UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
+        }
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission,
+                /*requirePermissionWhenSameUser= */ false)) {
+            return;
+        }
+        final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId);
+        if (isSameProfileGroup
+                && hasPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES)) {
+            return;
+        }
+        String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage(
+                message, requireFullPermission, isSameProfileGroup);
+        Slog.w(TAG, errorMessage);
+        throw new SecurityException(errorMessage);
+    }
+
+    private boolean hasCrossUserPermission(
+            int callingUid, int callingUserId, int userId, boolean requireFullPermission,
+            boolean requirePermissionWhenSameUser) {
+        if (!requirePermissionWhenSameUser && userId == callingUserId) {
+            return true;
+        }
+        if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) {
+            return true;
+        }
+        if (requireFullPermission) {
+            return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        }
+        return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS);
+    }
+
+    private boolean hasPermission(String permission) {
+        return mContext.checkCallingOrSelfPermission(permission)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return UserManagerService.getInstance().isSameProfileGroup(callerUserId, userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private static String buildInvalidCrossUserPermissionMessage(
+            String message, boolean requireFullPermission) {
+        StringBuilder builder = new StringBuilder();
+        if (message != null) {
+            builder.append(message);
+            builder.append(": ");
+        }
+        builder.append("Requires ");
+        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        if (requireFullPermission) {
+            builder.append(".");
+            return builder.toString();
+        }
+        builder.append(" or ");
+        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+        builder.append(".");
+        return builder.toString();
+    }
+
+    private static String buildInvalidCrossUserOrProfilePermissionMessage(
+            String message, boolean requireFullPermission, boolean isSameProfileGroup) {
+        StringBuilder builder = new StringBuilder();
+        if (message != null) {
+            builder.append(message);
+            builder.append(": ");
+        }
+        builder.append("Requires ");
+        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        if (requireFullPermission) {
+            builder.append(".");
+            return builder.toString();
+        }
+        builder.append(" or ");
+        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+        if (isSameProfileGroup) {
+            builder.append(" or ");
+            builder.append(android.Manifest.permission.INTERACT_ACROSS_PROFILES);
+        }
+        builder.append(".");
+        return builder.toString();
     }
 
     @GuardedBy({"mSettings.mLock", "mLock"})
@@ -4215,6 +4322,17 @@
             PermissionManagerService.this.enforceCrossUserPermission(callingUid, userId,
                     requireFullPermission, checkShell, requirePermissionWhenSameUser, message);
         }
+
+        @Override
+        public void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+                boolean requireFullPermission, boolean checkShell, String message) {
+            PermissionManagerService.this.enforceCrossUserOrProfilePermission(callingUid,
+                    userId,
+                    requireFullPermission,
+                    checkShell,
+                    message);
+        }
+
         @Override
         public void enforceGrantRevokeRuntimePermissionPermissions(String message) {
             PermissionManagerService.this.enforceGrantRevokeRuntimePermissionPermissions(message);
@@ -4453,8 +4571,8 @@
 
         @Override
         public void onNewUserCreated(int userId) {
+            mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId);
             synchronized (mLock) {
-                mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId);
                 // NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG
                 PermissionManagerService.this.updateAllPermissions(
                         StorageManager.UUID_PRIVATE_INTERNAL, true, mDefaultPermissionCallback);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 0f22619..58a9f42 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -271,6 +271,15 @@
      */
     public abstract void enforceCrossUserPermission(int callingUid, int userId,
             boolean requireFullPermission, boolean checkShell, @NonNull String message);
+
+    /**
+     * Similar to {@link #enforceCrossUserPermission(int, int, boolean, boolean, String)}
+     * but also allows INTERACT_ACROSS_PROFILES permission if calling user and {@code userId} are
+     * in the same profile group.
+     */
+    public abstract void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+            boolean requireFullPermission, boolean checkShell, @NonNull String message);
+
     /**
      * @see #enforceCrossUserPermission(int, int, boolean, boolean, String)
      * @param requirePermissionWhenSameUser When {@code true}, still require the cross user
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index e7269a6..a86c8d7 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -58,6 +58,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
+import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.IntPair;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.FgThread;
@@ -68,7 +69,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
 
 /**
  * This is a permission policy that governs over all permission mechanism
@@ -280,7 +281,7 @@
             if (DEBUG) Slog.i(LOG_TAG, "defaultPermsWereGrantedSinceBoot(" + userId + ")");
 
             // Now call into the permission controller to apply policy around permissions
-            final CountDownLatch latch = new CountDownLatch(1);
+            final AndroidFuture<Boolean> future = new AndroidFuture<>();
 
             // We need to create a local manager that does not schedule work on the main
             // there as we are on the main thread and want to block until the work is
@@ -290,22 +291,22 @@
                             getUserContext(getContext(), UserHandle.of(userId)),
                             FgThread.getHandler());
             permissionControllerManager.grantOrUpgradeDefaultRuntimePermissions(
-                    FgThread.getExecutor(),
-                    (Boolean success) -> {
-                        if (!success) {
+                    FgThread.getExecutor(), successful -> {
+                        if (successful) {
+                            future.complete(null);
+                        } else {
                             // We are in an undefined state now, let us crash and have
                             // rescue party suggest a wipe to recover to a good one.
-                            final String message = "Error granting/upgrading runtime permissions";
+                            final String message = "Error granting/upgrading runtime permissions"
+                                    + " for user " + userId;
                             Slog.wtf(LOG_TAG, message);
-                            throw new IllegalStateException(message);
+                            future.completeExceptionally(new IllegalStateException(message));
                         }
-                        latch.countDown();
-                    }
-            );
+                    });
             try {
-                latch.await();
-            } catch (InterruptedException e) {
-                /* ignore */
+                future.get();
+            } catch (InterruptedException | ExecutionException e) {
+                throw new IllegalStateException(e);
             }
 
             permissionControllerManager.updateUserSensitive();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5a124a7..ea83adb 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -562,10 +562,6 @@
     private boolean mScreenshotChordPowerKeyTriggered;
     private long mScreenshotChordPowerKeyTime;
 
-    private static final long MOVING_DISPLAY_TO_TOP_DURATION_MILLIS = 10;
-    private volatile boolean mMovingDisplayToTopKeyTriggered;
-    private volatile long mMovingDisplayToTopKeyTime;
-
     // Ringer toggle should reuse timing and triggering from screenshot power and a11y vol up
     private int mRingerToggleChord = VOLUME_HUSH_OFF;
 
@@ -633,7 +629,6 @@
     private static final int MSG_POWER_VERY_LONG_PRESS = 25;
     private static final int MSG_NOTIFY_USER_ACTIVITY = 26;
     private static final int MSG_RINGER_TOGGLE_CHORD = 27;
-    private static final int MSG_MOVE_DISPLAY_TO_TOP = 28;
 
     private class PolicyHandler extends Handler {
         @Override
@@ -697,7 +692,7 @@
                     accessibilityShortcutActivated();
                     break;
                 case MSG_BUGREPORT_TV:
-                    requestFullBugreport();
+                    requestFullBugreportOrLaunchHandlerApp();
                     break;
                 case MSG_ACCESSIBILITY_TV:
                     if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable(false)) {
@@ -723,10 +718,6 @@
                 case MSG_RINGER_TOGGLE_CHORD:
                     handleRingerChordGesture();
                     break;
-                case MSG_MOVE_DISPLAY_TO_TOP:
-                    mWindowManagerFuncs.moveDisplayToTop(msg.arg1);
-                    mMovingDisplayToTopKeyTriggered = false;
-                    break;
             }
         }
     }
@@ -2545,36 +2536,6 @@
     @Override
     public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
             int policyFlags) {
-        final long result = interceptKeyBeforeDispatchingInner(focusedToken, event, policyFlags);
-        final int eventDisplayId = event.getDisplayId();
-        if (result == 0 && !mPerDisplayFocusEnabled
-                && eventDisplayId != INVALID_DISPLAY && eventDisplayId != mTopFocusedDisplayId) {
-            // An event is targeting a non-focused display. Try to move the display to top so that
-            // it can become the focused display to interact with the user.
-            final long eventDownTime = event.getDownTime();
-            if (mMovingDisplayToTopKeyTime < eventDownTime) {
-                // We have not handled this event yet. Move the display to top, and then tell
-                // dispatcher to try again later.
-                mMovingDisplayToTopKeyTime = eventDownTime;
-                mMovingDisplayToTopKeyTriggered = true;
-                mHandler.sendMessage(
-                        mHandler.obtainMessage(MSG_MOVE_DISPLAY_TO_TOP, eventDisplayId, 0));
-                return MOVING_DISPLAY_TO_TOP_DURATION_MILLIS;
-            } else if (mMovingDisplayToTopKeyTriggered) {
-                // The message has not been handled yet. Tell dispatcher to try again later.
-                return MOVING_DISPLAY_TO_TOP_DURATION_MILLIS;
-            }
-            // The target display is still not the top focused display. Drop the event because the
-            // display may not contain any window which can receive keys.
-            Slog.w(TAG, "Dropping key targeting non-focused display #" + eventDisplayId
-                    + " keyCode=" + KeyEvent.keyCodeToString(event.getKeyCode()));
-            return -1;
-        }
-        return result;
-    }
-
-    private long interceptKeyBeforeDispatchingInner(IBinder focusedToken, KeyEvent event,
-            int policyFlags) {
         final boolean keyguardOn = keyguardOn();
         final int keyCode = event.getKeyCode();
         final int repeatCount = event.getRepeatCount();
@@ -3061,12 +3022,14 @@
         return mAccessibilityTvScheduled;
     }
 
-    private void requestFullBugreport() {
+    private void requestFullBugreportOrLaunchHandlerApp() {
         if ("1".equals(SystemProperties.get("ro.debuggable"))
                 || Settings.Global.getInt(mContext.getContentResolver(),
                         Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1) {
             try {
-                ActivityManager.getService().requestFullBugReport();
+                if (!ActivityManager.getService().launchBugReportHandlerApp()) {
+                    ActivityManager.getService().requestFullBugReport();
+                }
             } catch (RemoteException e) {
                 Slog.e(TAG, "Error taking bugreport", e);
             }
@@ -3613,7 +3576,6 @@
         final boolean canceled = event.isCanceled();
         final int keyCode = event.getKeyCode();
         final int displayId = event.getDisplayId();
-
         final boolean isInjected = (policyFlags & WindowManagerPolicy.FLAG_INJECTED) != 0;
 
         // If screen is off then we treat the case where the keyguard is open but hidden
@@ -4035,6 +3997,23 @@
                     PowerManager.WAKE_REASON_WAKE_KEY, "android.policy:KEY");
         }
 
+        if ((result & ACTION_PASS_TO_USER) != 0) {
+            // If the key event is targeted to a specific display, then the user is interacting with
+            // that display. Therefore, give focus to the display that the user is interacting with.
+            if (!mPerDisplayFocusEnabled
+                    && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) {
+                // An event is targeting a non-focused display. Move the display to top so that
+                // it can become the focused display to interact with the user.
+                // This should be done asynchronously, once the focus logic is fully moved to input
+                // from windowmanager. Currently, we need to ensure the setInputWindows completes,
+                // which would force the focus event to be queued before the current key event.
+                // TODO(b/70668286): post call to 'moveDisplayToTop' to mHandler instead
+                Log.i(TAG, "Moving non-focused display " + displayId + " to top "
+                        + "because a key is targeting it");
+                mWindowManagerFuncs.moveDisplayToTop(displayId);
+            }
+        }
+
         return result;
     }
 
@@ -5213,7 +5192,7 @@
                     final Intent dock = createHomeDockIntent();
                     if (dock != null) {
                         int result = ActivityTaskManager.getService()
-                                .startActivityAsUser(null, null, dock,
+                                .startActivityAsUser(null, mContext.getBasePackageName(), dock,
                                         dock.resolveTypeIfNeeded(mContext.getContentResolver()),
                                         null, null, 0,
                                         ActivityManager.START_FLAG_ONLY_IF_NEEDED,
@@ -5224,7 +5203,7 @@
                     }
                 }
                 int result = ActivityTaskManager.getService()
-                        .startActivityAsUser(null, null, mHomeIntent,
+                        .startActivityAsUser(null, mContext.getBasePackageName(), mHomeIntent,
                                 mHomeIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
                                 null, null, 0,
                                 ActivityManager.START_FLAG_ONLY_IF_NEEDED,
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 88c1564..9f592b8 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -62,7 +62,7 @@
 
     private static final String TAG = "RollbackManager";
 
-    @IntDef(flag = true, prefix = { "ROLLBACK_STATE_" }, value = {
+    @IntDef(prefix = { "ROLLBACK_STATE_" }, value = {
             ROLLBACK_STATE_ENABLING,
             ROLLBACK_STATE_AVAILABLE,
             ROLLBACK_STATE_COMMITTED,
@@ -92,6 +92,19 @@
      */
     static final int ROLLBACK_STATE_DELETED = 4;
 
+    @IntDef(flag = true, prefix = { "MATCH_" }, value = {
+            MATCH_APK_IN_APEX,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface RollbackInfoFlags {}
+
+    /**
+     * {@link RollbackInfo} flag: include {@code RollbackInfo} packages that are apk-in-apex.
+     * These packages do not have their own sessions. They are embedded in an apex which has a
+     * session id.
+     */
+    static final int MATCH_APK_IN_APEX = 1;
+
     /**
      * The session ID for the staged session if this rollback data represents a staged session,
      * {@code -1} otherwise.
@@ -323,8 +336,8 @@
                 new VersionedPackage(packageName, newVersion),
                 new VersionedPackage(packageName, installedVersion),
                 new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
-                isApex, new IntArray(), new SparseLongArray() /* ceSnapshotInodes */,
-                rollbackDataPolicy);
+                isApex, false /* isApkInApex */, new IntArray(),
+                new SparseLongArray() /* ceSnapshotInodes */, rollbackDataPolicy);
 
         synchronized (mLock) {
             info.getPackages().add(packageRollbackInfo);
@@ -334,6 +347,30 @@
     }
 
     /**
+     * Enables this rollback for the provided apk-in-apex.
+     *
+     * @return boolean True if the rollback was enabled successfully for the specified package.
+     */
+    boolean enableForPackageInApex(String packageName, long installedVersion,
+            int rollbackDataPolicy) {
+        // TODO(b/142712057): Extract the new version number of apk-in-apex
+        // The new version for the apk-in-apex is set to 0 for now. If the package is then further
+        // updated via non-staged install flow, then RollbackManagerServiceImpl#onPackageReplaced()
+        // will be called and this rollback will be deleted. Other ways of package update have not
+        // been handled yet.
+        PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(
+                new VersionedPackage(packageName, 0 /* newVersion */),
+                new VersionedPackage(packageName, installedVersion),
+                new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
+                false /* isApex */, true /* isApkInApex */, new IntArray(),
+                new SparseLongArray() /* ceSnapshotInodes */, rollbackDataPolicy);
+        synchronized (mLock) {
+            info.getPackages().add(packageRollbackInfo);
+        }
+        return true;
+    }
+
+    /**
      * Snapshots user data for the provided package and user ids. Does nothing if this rollback is
      * not in the ENABLING state.
      */
@@ -428,6 +465,11 @@
                         parentSessionId);
 
                 for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
+                    if (pkgRollbackInfo.isApkInApex()) {
+                        // No need to issue a downgrade install request for apk-in-apex. It will
+                        // be rolled back when its parent apex is downgraded.
+                        continue;
+                    }
                     PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                             PackageInstaller.SessionParams.MODE_FULL_INSTALL);
                     String installerPackageName = mInstallerPackageName;
@@ -453,7 +495,8 @@
                             this, pkgRollbackInfo.getPackageName());
                     if (packageCodePaths == null) {
                         sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
-                                "Backup copy of package inaccessible");
+                                "Backup copy of package: "
+                                        + pkgRollbackInfo.getPackageName() + " is inaccessible");
                         return;
                     }
 
@@ -696,9 +739,30 @@
         }
     }
 
-    int getPackageCount() {
+    /**
+     * Returns the number of {@link PackageRollbackInfo} we are storing in this {@link Rollback}
+     * instance. By default, this method does not include apk-in-apex package in the count.
+     *
+     * @param flags Apk-in-apex packages can be included in the count by passing
+     * {@link Rollback#MATCH_APK_IN_APEX}
+     *
+     * @return Counts number of {@link PackageRollbackInfo} stored in the {@link Rollback}
+     * according to {@code flags} passed
+     */
+    int getPackageCount(@RollbackInfoFlags int flags) {
         synchronized (mLock) {
-            return info.getPackages().size();
+            List<PackageRollbackInfo> packages = info.getPackages();
+            if ((flags & MATCH_APK_IN_APEX) != 0) {
+                return packages.size();
+            }
+
+            int packagesWithoutApkInApex = 0;
+            for (PackageRollbackInfo rollbackInfo : packages) {
+                if (!rollbackInfo.isApkInApex()) {
+                    packagesWithoutApkInApex++;
+                }
+            }
+            return packagesWithoutApkInApex;
         }
     }
 
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index e29d1a7..8f8a5c4 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -891,9 +891,36 @@
         }
 
         ApplicationInfo appInfo = pkgInfo.applicationInfo;
-        return rollback.enableForPackage(packageName, newPackage.versionCode,
+        boolean success = rollback.enableForPackage(packageName, newPackage.versionCode,
                 pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
                 appInfo.splitSourceDirs, session.rollbackDataPolicy);
+        if (!success) {
+            return success;
+        }
+
+        if (isApex) {
+            // Check if this apex contains apks inside it. If true, then they should be added as
+            // a RollbackPackageInfo into this rollback
+            final PackageManagerInternal pmi = LocalServices.getService(
+                    PackageManagerInternal.class);
+            List<String> apksInApex = pmi.getApksInApex(packageName);
+            for (String apkInApex : apksInApex) {
+                // Get information about the currently installed package.
+                final PackageInfo apkPkgInfo;
+                try {
+                    apkPkgInfo = getPackageInfo(apkInApex);
+                } catch (PackageManager.NameNotFoundException e) {
+                    // TODO: Support rolling back fresh package installs rather than
+                    // fail here. Test this case.
+                    Slog.e(TAG, apkInApex + " is not installed");
+                    return false;
+                }
+                success = rollback.enableForPackageInApex(
+                        apkInApex, apkPkgInfo.getLongVersionCode(), session.rollbackDataPolicy);
+                if (!success) return success;
+            }
+        }
+        return true;
     }
 
     @Override
@@ -907,9 +934,13 @@
         getHandler().post(() -> {
             snapshotUserDataInternal(packageName, userIds);
             restoreUserDataInternal(packageName, userIds, appId, seInfo);
-            final PackageManagerInternal pmi = LocalServices.getService(
-                    PackageManagerInternal.class);
-            pmi.finishPackageInstall(token, false);
+            // When this method is called as part of the install flow, a positive token number is
+            // passed to it. Need to notify the PackageManager when we are done.
+            if (token > 0) {
+                final PackageManagerInternal pmi = LocalServices.getService(
+                        PackageManagerInternal.class);
+                pmi.finishPackageInstall(token, false);
+            }
         });
     }
 
@@ -1195,7 +1226,11 @@
             return null;
         }
 
-        if (rollback.getPackageCount() != newRollback.getPackageSessionIdCount()) {
+        // We are checking if number of packages (excluding apk-in-apex) we enabled for rollback is
+        // equal to the number of sessions we are installing, to ensure we didn't skip enabling
+        // of any sessions. If we successfully enable an apex, then we can assume we enabled
+        // rollback for the embedded apk-in-apex, if any.
+        if (rollback.getPackageCount(0 /*flags*/) != newRollback.getPackageSessionIdCount()) {
             Slog.e(TAG, "Failed to enable rollback for all packages in session.");
             rollback.delete(mAppDataRollbackHelper);
             return null;
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index df75a29..bbcd0de 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -341,6 +341,7 @@
         json.put("pendingRestores", convertToJsonArray(pendingRestores));
 
         json.put("isApex", info.isApex());
+        json.put("isApkInApex", info.isApkInApex());
 
         // Field is named 'installedUsers' for legacy reasons.
         json.put("installedUsers", convertToJsonArray(snapshottedUsers));
@@ -364,6 +365,7 @@
                 json.getJSONArray("pendingRestores"));
 
         final boolean isApex = json.getBoolean("isApex");
+        final boolean isApkInApex = json.getBoolean("isApkInApex");
 
         // Field is named 'installedUsers' for legacy reasons.
         final IntArray snapshottedUsers = convertToIntArray(json.getJSONArray("installedUsers"));
@@ -375,8 +377,8 @@
                 PackageManager.RollbackDataPolicy.RESTORE);
 
         return new PackageRollbackInfo(versionRolledBackFrom, versionRolledBackTo,
-                pendingBackups, pendingRestores, isApex, snapshottedUsers, ceSnapshotInodes,
-                rollbackDataPolicy);
+                pendingBackups, pendingRestores, isApex, isApkInApex, snapshottedUsers,
+                ceSnapshotInodes, rollbackDataPolicy);
     }
 
     private static JSONArray versionedPackagesToJson(List<VersionedPackage> packages)
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index 0b89646..a641f06 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -21,8 +21,10 @@
 import android.hardware.audio.common.V2_0.Uuid;
 import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
 import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
+import android.hardware.soundtrigger.V2_3.Properties;
 import android.media.audio.common.AudioConfig;
 import android.media.audio.common.AudioOffloadInfo;
+import android.media.soundtrigger_middleware.AudioCapabilities;
 import android.media.soundtrigger_middleware.ConfidenceLevel;
 import android.media.soundtrigger_middleware.ModelParameter;
 import android.media.soundtrigger_middleware.ModelParameterRange;
@@ -69,6 +71,15 @@
         return aidlProperties;
     }
 
+    static @NonNull SoundTriggerModuleProperties hidl2aidlProperties(
+            @NonNull Properties hidlProperties) {
+        SoundTriggerModuleProperties aidlProperties = hidl2aidlProperties(hidlProperties.base);
+        aidlProperties.supportedModelArch = hidlProperties.supportedModelArch;
+        aidlProperties.audioCapabilities =
+                hidl2aidlAudioCapabilities(hidlProperties.audioCapabilities);
+        return aidlProperties;
+    }
+
     static @NonNull
     String hidl2aidlUuid(@NonNull Uuid hidlUuid) {
         if (hidlUuid.node == null || hidlUuid.node.length != 6) {
@@ -201,16 +212,17 @@
         return hidlModel;
     }
 
-    static @NonNull
-    ISoundTriggerHw.RecognitionConfig aidl2hidlRecognitionConfig(
+    static @NonNull android.hardware.soundtrigger.V2_3.RecognitionConfig aidl2hidlRecognitionConfig(
             @NonNull RecognitionConfig aidlConfig) {
-        ISoundTriggerHw.RecognitionConfig hidlConfig = new ISoundTriggerHw.RecognitionConfig();
-        hidlConfig.header.captureRequested = aidlConfig.captureRequested;
+        android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
+                new android.hardware.soundtrigger.V2_3.RecognitionConfig();
+        hidlConfig.base.header.captureRequested = aidlConfig.captureRequested;
         for (PhraseRecognitionExtra aidlPhraseExtra : aidlConfig.phraseRecognitionExtras) {
-            hidlConfig.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
+            hidlConfig.base.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
         }
-        hidlConfig.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
+        hidlConfig.base.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
                 "SoundTrigger RecognitionConfig");
+        hidlConfig.audioCapabilities = aidlConfig.audioCapabilities;
         return hidlConfig;
     }
 
@@ -387,4 +399,17 @@
                 return android.hardware.soundtrigger.V2_3.ModelParameter.INVALID;
         }
     }
+
+    static int hidl2aidlAudioCapabilities(int hidlCapabilities) {
+        int aidlCapabilities = 0;
+        if ((hidlCapabilities
+                & android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION) != 0) {
+            aidlCapabilities |= AudioCapabilities.ECHO_CANCELLATION;
+        }
+        if ((hidlCapabilities
+                & android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION) != 0) {
+            aidlCapabilities |= AudioCapabilities.NOISE_SUPPRESSION;
+        }
+        return aidlCapabilities;
+    }
 }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
index f0a0d83..a42d292 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
@@ -66,12 +66,25 @@
         return model_2_0;
     }
 
-    static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_1_to_2_0(
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config) {
+    static android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_3_to_2_1(
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config) {
+        return config.base;
+    }
+
+    static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_3_to_2_0(
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config) {
         android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
-                config.header;
+                config.base.header;
         // Note: this mutates the input!
-        config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.data);
+        config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.base.data);
         return config_2_0;
     }
+
+    static android.hardware.soundtrigger.V2_3.Properties convertProperties_2_0_to_2_3(
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties) {
+        android.hardware.soundtrigger.V2_3.Properties properties_2_3 =
+                new android.hardware.soundtrigger.V2_3.Properties();
+        properties_2_3.base = properties;
+        return properties_2_3;
+    }
 }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
index 81252c9..8b434bd 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
@@ -16,7 +16,6 @@
 
 package com.android.server.soundtrigger_middleware;
 
-import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
 import android.hardware.soundtrigger.V2_3.ModelParameterRange;
 import android.hidl.base.V1_0.IBase;
 import android.os.IHwBinder;
@@ -54,9 +53,10 @@
  */
 public interface ISoundTriggerHw2 {
     /**
-     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getProperties(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback
+     * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#getPropertiesEx(
+     * android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getPropertiesExCallback)
      */
-    android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties();
+    android.hardware.soundtrigger.V2_3.Properties getProperties();
 
     /**
      * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel,
@@ -92,12 +92,12 @@
     void stopAllRecognitions();
 
     /**
-     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#startRecognition_2_1(int,
-     * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig,
+     * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#startRecognition_2_3(int,
+     * android.hardware.soundtrigger.V2_3.RecognitionConfig,
      * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int)
      */
     void startRecognition(int modelHandle,
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config,
             SoundTriggerHw2Compat.Callback callback, int cookie);
 
     /**
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
index 4a852c4..2f087f4 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -112,18 +112,23 @@
     }
 
     @Override
-    public android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties() {
+    public android.hardware.soundtrigger.V2_3.Properties getProperties() {
         try {
             AtomicInteger retval = new AtomicInteger(-1);
-            AtomicReference<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties>
+            AtomicReference<android.hardware.soundtrigger.V2_3.Properties>
                     properties =
                     new AtomicReference<>();
-            as2_0().getProperties(
-                    (r, p) -> {
-                        retval.set(r);
-                        properties.set(p);
-                    });
-            handleHalStatus(retval.get(), "getProperties");
+            try {
+                as2_3().getProperties_2_3(
+                        (r, p) -> {
+                            retval.set(r);
+                            properties.set(p);
+                        });
+            } catch (NotSupported e) {
+                // Fall-back to the 2.0 version:
+                return getProperties_2_0();
+            }
+            handleHalStatus(retval.get(), "getProperties_2_3");
             return properties.get();
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
@@ -212,16 +217,15 @@
 
     @Override
     public void startRecognition(int modelHandle,
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config,
             Callback callback, int cookie) {
         try {
             try {
-                int retval = as2_1().startRecognition_2_1(modelHandle, config,
-                        new SoundTriggerCallback(callback), cookie);
-                handleHalStatus(retval, "startRecognition_2_1");
+                int retval = as2_3().startRecognition_2_3(modelHandle, config);
+                handleHalStatus(retval, "startRecognition_2_3");
             } catch (NotSupported e) {
                 // Fall-back to the 2.0 version:
-                startRecognition_2_0(modelHandle, config, callback, cookie);
+                startRecognition_2_1(modelHandle, config, callback, cookie);
             }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
@@ -312,6 +316,21 @@
         return as2_0().interfaceDescriptor();
     }
 
+    private android.hardware.soundtrigger.V2_3.Properties getProperties_2_0()
+            throws RemoteException {
+        AtomicInteger retval = new AtomicInteger(-1);
+        AtomicReference<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties>
+                properties =
+                new AtomicReference<>();
+        as2_0().getProperties(
+                (r, p) -> {
+                    retval.set(r);
+                    properties.set(p);
+                });
+        handleHalStatus(retval.get(), "getProperties");
+        return Hw2CompatUtil.convertProperties_2_0_to_2_3(properties.get());
+    }
+
     private int loadSoundModel_2_0(
             android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
             Callback callback, int cookie)
@@ -349,13 +368,31 @@
         return handle.get();
     }
 
+    private void startRecognition_2_1(int modelHandle,
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config,
+            Callback callback, int cookie) {
+        try {
+            try {
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config_2_1 =
+                        Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_1(config);
+                int retval = as2_1().startRecognition_2_1(modelHandle, config_2_1,
+                        new SoundTriggerCallback(callback), cookie);
+                handleHalStatus(retval, "startRecognition_2_1");
+            } catch (NotSupported e) {
+                // Fall-back to the 2.0 version:
+                startRecognition_2_0(modelHandle, config, callback, cookie);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
     private void startRecognition_2_0(int modelHandle,
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config,
             Callback callback, int cookie)
             throws RemoteException {
-
         android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
-                Hw2CompatUtil.convertRecognitionConfig_2_1_to_2_0(config);
+                Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_0(config);
         int retval = as2_0().startRecognition(modelHandle, config_2_0,
                 new SoundTriggerCallback(callback), cookie);
         handleHalStatus(retval, "startRecognition");
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index 987c05f..5a06a2c 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.PermissionChecker;
 import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
@@ -32,6 +33,7 @@
 import android.media.soundtrigger_middleware.RecognitionStatus;
 import android.media.soundtrigger_middleware.SoundModel;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.Status;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.Log;
@@ -223,23 +225,48 @@
     }
 
     /**
-     * Throws a {@link SecurityException} if caller doesn't have the right permissions to use this
-     * service.
+     * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+     * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+     * caller temporarily doesn't have the right permissions to use this service.
      */
     private void checkPermissions() {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.RECORD_AUDIO,
-                "Caller must have the android.permission.RECORD_AUDIO permission.");
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD,
-                "Caller must have the android.permission.CAPTURE_AUDIO_HOTWORD permission.");
+        enforcePermission(Manifest.permission.RECORD_AUDIO);
+        enforcePermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD);
     }
 
     /**
-     * Throws a {@link SecurityException} if caller doesn't have the right permissions to preempt
-     * active sound trigger sessions.
+     * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+     * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+     * caller temporarily doesn't have the right permissions to preempt active sound trigger
+     * sessions.
      */
     private void checkPreemptPermissions() {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.PREEMPT_SOUND_TRIGGER,
-                "Caller must have the android.permission.PREEMPT_SOUND_TRIGGER permission.");
+        enforcePermission(Manifest.permission.PREEMPT_SOUND_TRIGGER);
+    }
+
+    /**
+     * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+     * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+     * caller temporarily doesn't have the given permission.
+     *
+     * @param permission The permission to check.
+     */
+    private void enforcePermission(String permission) {
+        final int status = PermissionChecker.checkCallingOrSelfPermissionForPreflight(mContext,
+                permission);
+        switch (status) {
+            case PermissionChecker.PERMISSION_GRANTED:
+                return;
+            case PermissionChecker.PERMISSION_DENIED:
+                throw new SecurityException(
+                        String.format("Caller must have the %s permission.", permission));
+            case PermissionChecker.PERMISSION_DENIED_APP_OP:
+                throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED,
+                        String.format("Caller must have the %s permission.", permission));
+            default:
+                throw new InternalServerError(
+                        new RuntimeException("Unexpected perimission check result."));
+        }
     }
 
     /** State of a sound model. */
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 81789e1..f024ede 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -401,10 +401,10 @@
                     notifyAbort();
                     return;
                 }
-                android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig hidlConfig =
+                android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
                         ConversionUtil.aidl2hidlRecognitionConfig(config);
-                hidlConfig.header.captureDevice = mSession.mDeviceHandle;
-                hidlConfig.header.captureHandle = mSession.mIoHandle;
+                hidlConfig.base.header.captureDevice = mSession.mDeviceHandle;
+                hidlConfig.base.header.captureHandle = mSession.mIoHandle;
                 mHalService.startRecognition(mHandle, hidlConfig, this, 0);
                 setState(ModelState.ACTIVE);
             }
diff --git a/services/core/java/com/android/server/stats/StatsPullAtomService.java b/services/core/java/com/android/server/stats/StatsPullAtomService.java
index e367f28..a75f1c2 100644
--- a/services/core/java/com/android/server/stats/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/StatsPullAtomService.java
@@ -16,11 +16,166 @@
 
 package com.android.server.stats;
 
-import android.content.Context;
-import android.util.Slog;
+import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+import static android.os.Process.getUidForPid;
+import static android.os.storage.VolumeInfo.TYPE_PRIVATE;
+import static android.os.storage.VolumeInfo.TYPE_PUBLIC;
 
+import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
+import static com.android.server.stats.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
+import static com.android.server.stats.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
+import static com.android.server.stats.ProcfsMemoryUtil.forEachPid;
+import static com.android.server.stats.ProcfsMemoryUtil.readCmdlineFromProcfs;
+import static com.android.server.stats.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManagerInternal;
+import android.app.AlarmManager;
+import android.app.AlarmManager.OnAlarmListener;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalOps;
+import android.app.AppOpsManager.HistoricalOpsRequest;
+import android.app.AppOpsManager.HistoricalPackageOps;
+import android.app.AppOpsManager.HistoricalUidOps;
+import android.app.INotificationManager;
+import android.app.ProcessMemoryState;
+import android.app.StatsManager;
+import android.app.StatsManager.PullAtomMetadata;
+import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.UidTraffic;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.net.ConnectivityManager;
+import android.net.INetworkStatsService;
+import android.net.Network;
+import android.net.NetworkRequest;
+import android.net.NetworkStats;
+import android.net.wifi.WifiManager;
+import android.os.BatteryStats;
+import android.os.BatteryStatsInternal;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CoolingDevice;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.IPullAtomCallback;
+import android.os.IStatsCompanionService;
+import android.os.IStatsd;
+import android.os.IStoraged;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.StatFs;
+import android.os.StatsLogEventWrapper;
+import android.os.SynchronousResultReceiver;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Temperature;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.connectivity.WifiActivityEnergyInfo;
+import android.os.storage.DiskInfo;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.provider.Settings;
+import android.stats.storage.StorageEnums;
+import android.telephony.ModemActivityInfo;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.StatsEvent;
+import android.util.StatsLog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.procstats.IProcessStats;
+import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.internal.os.BinderCallsStats.ExportedCallStat;
+import com.android.internal.os.KernelCpuSpeedReader;
+import com.android.internal.os.KernelCpuThreadReader;
+import com.android.internal.os.KernelCpuThreadReaderDiff;
+import com.android.internal.os.KernelCpuThreadReaderSettingsObserver;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
+import com.android.internal.os.KernelWakelockReader;
+import com.android.internal.os.KernelWakelockStats;
+import com.android.internal.os.LooperStats;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.os.StoragedUidIoStatsReader;
+import com.android.internal.util.DumpUtils;
+import com.android.server.BinderCallsStatsService;
+import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemServiceManager;
+import com.android.server.am.MemoryStatUtil.MemoryStat;
+import com.android.server.notification.NotificationManagerService;
+import com.android.server.role.RoleManagerInternal;
+import com.android.server.stats.IonMemoryUtil.IonAllocations;
+import com.android.server.stats.ProcfsMemoryUtil.MemorySnapshot;
+import com.android.server.storage.DiskStatsFileLogger;
+import com.android.server.storage.DiskStatsLoggingService;
+
+import com.google.android.collect.Sets;
+
+import libcore.io.IoUtils;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * SystemService containing PullAtomCallbacks that are registered with statsd.
@@ -31,13 +186,24 @@
     private static final String TAG = "StatsPullAtomService";
     private static final boolean DEBUG = true;
 
+    private final Object mNetworkStatsLock = new Object();
+    @GuardedBy("mNetworkStatsLock")
+    private INetworkStatsService mNetworkStatsService;
+    private final Object mThermalLock = new Object();
+    @GuardedBy("mThermalLock")
+    private IThermalService mThermalService;
+
+    private final Context mContext;
+    private StatsManager mStatsManager;
+
     public StatsPullAtomService(Context context) {
         super(context);
+        mContext = context;
     }
 
     @Override
     public void onStart() {
-        // No op.
+        mStatsManager = (StatsManager) mContext.getSystemService(Context.STATS_MANAGER);
     }
 
     @Override
@@ -54,5 +220,621 @@
         if (DEBUG) {
             Slog.d(TAG, "Registering all pullers with statsd");
         }
+        registerWifiBytesTransfer();
+        registerWifiBytesTransferBackground();
+        registerMobileBytesTransfer();
+        registerMobileBytesTransferBackground();
+        registerBluetoothBytesTransfer();
+        registerKernelWakelock();
+        registerCpuTimePerFreq();
+        registerCpuTimePerUid();
+        registerCpuTimePerUidFreq();
+        registerCpuActiveTime();
+        registerCpuClusterTime();
+        registerWifiActivityInfo();
+        registerModemActivityInfo();
+        registerBluetoothActivityInfo();
+        registerSystemElapsedRealtime();
+        registerSystemUptime();
+        registerRemainingBatteryCapacity();
+        registerFullBatteryCapacity();
+        registerBatteryVoltage();
+        registerBatteryLevel();
+        registerBatteryCycleCount();
+        registerProcessMemoryState();
+        registerProcessMemoryHighWaterMark();
+        registerProcessMemorySnapshot();
+        registerSystemIonHeapSize();
+        registerProcessSystemIonHeapSize();
+        registerTemperature();
+        registerCoolingDevice();
+        registerBinderCalls();
+        registerBinderCallsExceptions();
+        registerLooperStats();
+        registerDiskStats();
+        registerDirectoryUsage();
+        registerAppSize();
+        registerCategorySize();
+        registerNumFingerprintsEnrolled();
+        registerNumFacesEnrolled();
+        registerProcStats();
+        registerProcStatsPkgProc();
+        registerDiskIO();
+        registerPowerProfile();
+        registerProcessCpuTime();
+        registerCpuTimePerThreadFreq();
+        registerDeviceCalculatedPowerUse();
+        registerDeviceCalculatedPowerBlameUid();
+        registerDeviceCalculatedPowerBlameOther();
+        registerDebugElapsedClock();
+        registerDebugFailingElapsedClock();
+        registerBuildInformation();
+        registerRoleHolder();
+        registerDangerousPermissionState();
+        registerTimeZoneDataInfo();
+        registerExternalStorageInfo();
+        registerAppsOnExternalStorageInfo();
+        registerFaceSettings();
+        registerAppOps();
+        registerNotificationRemoteViews();
+        registerDangerousPermissionState();
+        registerDangerousPermissionStateSampled();
+    }
+
+    private INetworkStatsService getINetworkStatsService() {
+        synchronized (mNetworkStatsLock) {
+            if (mNetworkStatsService == null) {
+                mNetworkStatsService = INetworkStatsService.Stub.asInterface(
+                        ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+                if (mNetworkStatsService != null) {
+                    try {
+                        mNetworkStatsService.asBinder().linkToDeath(() -> {
+                            synchronized (mNetworkStatsLock) {
+                                mNetworkStatsService = null;
+                            }
+                        }, /* flags */ 0);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "linkToDeath with NetworkStatsService failed", e);
+                        mNetworkStatsService = null;
+                    }
+                }
+
+            }
+            return mNetworkStatsService;
+        }
+    }
+
+    private IThermalService getIThermalService() {
+        synchronized (mThermalLock) {
+            if (mThermalService == null) {
+                mThermalService = IThermalService.Stub.asInterface(
+                        ServiceManager.getService(Context.THERMAL_SERVICE));
+                if (mThermalService != null) {
+                    try {
+                        mThermalService.asBinder().linkToDeath(() -> {
+                            synchronized (mThermalLock) {
+                                mThermalService = null;
+                            }
+                        }, /* flags */ 0);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "linkToDeath with thermalService failed", e);
+                        mThermalService = null;
+                    }
+                }
+            }
+            return mThermalService;
+        }
+    }
+    private void registerWifiBytesTransfer() {
+        int tagId = StatsLog.WIFI_BYTES_TRANSFER;
+        PullAtomMetadata metaData = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {2, 3, 4, 5}).build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metaData,
+                (atomTag, data) -> pullWifiBytesTransfer(atomTag, data),
+                Executors.newSingleThreadExecutor()
+        );
+    }
+
+    private int pullWifiBytesTransfer(int atomTag, List<StatsEvent> pulledData) {
+        INetworkStatsService networkStatsService = getINetworkStatsService();
+        if (networkStatsService == null) {
+            Slog.e(TAG, "NetworkStats Service is not available!");
+            return StatsManager.PULL_SKIP;
+        }
+        long token = Binder.clearCallingIdentity();
+        try {
+            // TODO: Consider caching the following call to get BatteryStatsInternal.
+            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+            String[] ifaces = bs.getWifiIfaces();
+            if (ifaces.length == 0) {
+                return StatsManager.PULL_SKIP;
+            }
+            // Combine all the metrics per Uid into one record.
+            NetworkStats stats = networkStatsService.getDetailedUidStats(ifaces).groupedByUid();
+            addNetworkStats(atomTag, pulledData, stats, false);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Pulling netstats for wifi bytes has error", e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void addNetworkStats(
+            int tag, List<StatsEvent> ret, NetworkStats stats, boolean withFGBG) {
+        int size = stats.size();
+        NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
+        for (int j = 0; j < size; j++) {
+            stats.getValues(j, entry);
+            StatsEvent.Builder e = StatsEvent.newBuilder();
+            e.setAtomId(tag);
+            e.writeInt(entry.uid);
+            if (withFGBG) {
+                e.writeInt(entry.set);
+            }
+            e.writeLong(entry.rxBytes);
+            e.writeLong(entry.rxPackets);
+            e.writeLong(entry.txBytes);
+            e.writeLong(entry.txPackets);
+            ret.add(e.build());
+        }
+    }
+
+    private void registerWifiBytesTransferBackground() {
+        // No op.
+    }
+
+    private void pullWifiBytesTransferBackground() {
+        // No op.
+    }
+
+    private void registerMobileBytesTransfer() {
+        // No op.
+    }
+
+    private void pullMobileBytesTransfer() {
+        // No op.
+    }
+
+    private void registerMobileBytesTransferBackground() {
+        // No op.
+    }
+
+    private void pullMobileBytesTransferBackground() {
+        // No op.
+    }
+
+    private void registerBluetoothBytesTransfer() {
+        // No op.
+    }
+
+    private void pullBluetoothBytesTransfer() {
+        // No op.
+    }
+
+    private void registerKernelWakelock() {
+        // No op.
+    }
+
+    private void pullKernelWakelock() {
+        // No op.
+    }
+
+    private void registerCpuTimePerFreq() {
+        // No op.
+    }
+
+    private void pullCpuTimePerFreq() {
+        // No op.
+    }
+
+    private void registerCpuTimePerUid() {
+        // No op.
+    }
+
+    private void pullCpuTimePerUid() {
+        // No op.
+    }
+
+    private void registerCpuTimePerUidFreq() {
+        // No op.
+    }
+
+    private void pullCpuTimeperUidFreq() {
+        // No op.
+    }
+
+    private void registerCpuActiveTime() {
+        // No op.
+    }
+
+    private void pullCpuActiveTime() {
+        // No op.
+    }
+
+    private void registerCpuClusterTime() {
+        // No op.
+    }
+
+    private int pullCpuClusterTime() {
+        return 0;
+    }
+
+    private void registerWifiActivityInfo() {
+        // No op.
+    }
+
+    private void pullWifiActivityInfo() {
+        // No op.
+    }
+
+    private void registerModemActivityInfo() {
+        // No op.
+    }
+
+    private void pullModemActivityInfo() {
+        // No op.
+    }
+
+    private void registerBluetoothActivityInfo() {
+        // No op.
+    }
+
+    private void pullBluetoothActivityInfo() {
+        // No op.
+    }
+
+    private void registerSystemElapsedRealtime() {
+        // No op.
+    }
+
+    private void pullSystemElapsedRealtime() {
+        // No op.
+    }
+
+    private void registerSystemUptime() {
+        // No op.
+    }
+
+    private void pullSystemUptime() {
+        // No op.
+    }
+
+    private void registerRemainingBatteryCapacity() {
+        // No op.
+    }
+
+    private void pullRemainingBatteryCapacity() {
+        // No op.
+    }
+
+    private void registerFullBatteryCapacity() {
+        // No op.
+    }
+
+    private void pullFullBatteryCapacity() {
+        // No op.
+    }
+
+    private void registerBatteryVoltage() {
+        // No op.
+    }
+
+    private void pullBatteryVoltage() {
+        // No op.
+    }
+
+    private void registerBatteryLevel() {
+        // No op.
+    }
+
+    private void pullBatteryLevel() {
+        // No op.
+    }
+
+    private void registerBatteryCycleCount() {
+        // No op.
+    }
+
+    private void pullBatteryCycleCount() {
+        // No op.
+    }
+
+    private void registerProcessMemoryState() {
+        // No op.
+    }
+
+    private void pullProcessMemoryState() {
+        // No op.
+    }
+
+    private void registerProcessMemoryHighWaterMark() {
+        // No op.
+    }
+
+    private void pullProcessMemoryHighWaterMark() {
+        // No op.
+    }
+
+    private void registerProcessMemorySnapshot() {
+        // No op.
+    }
+
+    private void pullProcessMemorySnapshot() {
+        // No op.
+    }
+
+    private void registerSystemIonHeapSize() {
+        // No op.
+    }
+
+    private void pullSystemIonHeapSize() {
+        // No op.
+    }
+
+    private void registerProcessSystemIonHeapSize() {
+        // No op.
+    }
+
+    private void pullProcessSystemIonHeapSize() {
+        // No op.
+    }
+
+    private void registerTemperature() {
+        // No op.
+    }
+
+    private void pullTemperature() {
+        // No op.
+    }
+
+    private void registerCoolingDevice() {
+        // No op.
+    }
+
+    private void pullCooldownDevice() {
+        // No op.
+    }
+
+    private void registerBinderCalls() {
+        // No op.
+    }
+
+    private void pullBinderCalls() {
+        // No op.
+    }
+
+    private void registerBinderCallsExceptions() {
+        // No op.
+    }
+
+    private void pullBinderCallsExceptions() {
+        // No op.
+    }
+
+    private void registerLooperStats() {
+        // No op.
+    }
+
+    private void pullLooperStats() {
+        // No op.
+    }
+
+    private void registerDiskStats() {
+        // No op.
+    }
+
+    private void pullDiskStats() {
+        // No op.
+    }
+
+    private void registerDirectoryUsage() {
+        // No op.
+    }
+
+    private void pullDirectoryUsage() {
+        // No op.
+    }
+
+    private void registerAppSize() {
+        // No op.
+    }
+
+    private void pullAppSize() {
+        // No op.
+    }
+
+    private void registerCategorySize() {
+        // No op.
+    }
+
+    private void pullCategorySize() {
+        // No op.
+    }
+
+    private void registerNumFingerprintsEnrolled() {
+        // No op.
+    }
+
+    private void pullNumFingerprintsEnrolled() {
+        // No op.
+    }
+
+    private void registerNumFacesEnrolled() {
+        // No op.
+    }
+
+    private void pullNumFacesEnrolled() {
+        // No op.
+    }
+
+    private void registerProcStats() {
+        // No op.
+    }
+
+    private void pullProcStats() {
+        // No op.
+    }
+
+    private void registerProcStatsPkgProc() {
+        // No op.
+    }
+
+    private void pullProcStatsPkgProc() {
+        // No op.
+    }
+
+    private void registerDiskIO() {
+        // No op.
+    }
+
+    private void pullDiskIO() {
+        // No op.
+    }
+
+    private void registerPowerProfile() {
+        // No op.
+    }
+
+    private void pullPowerProfile() {
+        // No op.
+    }
+
+    private void registerProcessCpuTime() {
+        // No op.
+    }
+
+    private void pullProcessCpuTime() {
+        // No op.
+    }
+
+    private void registerCpuTimePerThreadFreq() {
+        // No op.
+    }
+
+    private void pullCpuTimePerThreadFreq() {
+        // No op.
+    }
+
+    private void registerDeviceCalculatedPowerUse() {
+        // No op.
+    }
+
+    private void pullDeviceCalculatedPowerUse() {
+        // No op.
+    }
+
+    private void registerDeviceCalculatedPowerBlameUid() {
+        // No op.
+    }
+
+    private void pullDeviceCalculatedPowerBlameUid() {
+        // No op.
+    }
+
+    private void registerDeviceCalculatedPowerBlameOther() {
+        // No op.
+    }
+
+    private void pullDeviceCalculatedPowerBlameOther() {
+        // No op.
+    }
+
+    private void registerDebugElapsedClock() {
+        // No op.
+    }
+
+    private void pullDebugElapsedClock() {
+        // No op.
+    }
+
+    private void registerDebugFailingElapsedClock() {
+        // No op.
+    }
+
+    private void pullDebugFailingElapsedClock() {
+        // No op.
+    }
+
+    private void registerBuildInformation() {
+        // No op.
+    }
+
+    private void pullBuildInformation() {
+        // No op.
+    }
+
+    private void registerRoleHolder() {
+        // No op.
+    }
+
+    private void pullRoleHolder() {
+        // No op.
+    }
+
+    private void registerDangerousPermissionState() {
+        // No op.
+    }
+
+    private void pullDangerousPermissionState() {
+        // No op.
+    }
+
+    private void registerTimeZoneDataInfo() {
+        // No op.
+    }
+
+    private void pullTimeZoneDataInfo() {
+        // No op.
+    }
+
+    private void registerExternalStorageInfo() {
+        // No op.
+    }
+
+    private void pullExternalStorageInfo() {
+        // No op.
+    }
+
+    private void registerAppsOnExternalStorageInfo() {
+        // No op.
+    }
+
+    private void pullAppsOnExternalStorageInfo() {
+        // No op.
+    }
+
+    private void registerFaceSettings() {
+        // No op.
+    }
+
+    private void pullRegisterFaceSettings() {
+        // No op.
+    }
+
+    private void registerAppOps() {
+        // No op.
+    }
+
+    private void pullAppOps() {
+        // No op.
+    }
+
+    private void registerNotificationRemoteViews() {
+        // No op.
+    }
+
+    private void pullNotificationRemoteViews() {
+        // No op.
+    }
+
+    private void registerDangerousPermissionStateSampled() {
+        // No op.
+    }
+
+    private void pullDangerousPermissionStateSampled() {
+        // No op.
     }
 }
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index b4f4eca..c964795 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -21,8 +21,7 @@
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import java.io.PrintWriter;
 
@@ -73,9 +72,6 @@
 
         /** Release the wake lock acquired by a call to {@link #acquireWakeLock()}. */
         void releaseWakeLock();
-
-        /** Send the supplied intent as a stick broadcast. */
-        void sendStickyBroadcast(@NonNull Intent intent);
     }
 
     /** Initialize the strategy. */
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
index 42d59d5..9b89d94 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
@@ -20,11 +20,9 @@
 import android.app.AlarmManager;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.Intent;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
 
@@ -112,11 +110,6 @@
         mWakeLock.release();
     }
 
-    @Override
-    public void sendStickyBroadcast(@NonNull Intent intent) {
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
     private void checkWakeLockHeld() {
         if (!mWakeLock.isHeld()) {
             Slog.wtf(TAG, "WakeLock " + mWakeLock + " not held");
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 02656ea..e95fc4a 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -23,11 +23,9 @@
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
-import android.telephony.TelephonyManager;
+import android.os.TimestampedValue;
 import android.util.LocalLog;
 import android.util.Slog;
-import android.util.TimestampedValue;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -535,17 +533,6 @@
         } else {
             mLastAutoSystemClockTimeSet = null;
         }
-
-        // Historically, Android has sent a TelephonyManager.ACTION_NETWORK_SET_TIME broadcast only
-        // when setting the time using NITZ.
-        if (origin == ORIGIN_PHONE) {
-            // Send a broadcast that telephony code used to send after setting the clock.
-            // TODO Remove this broadcast as soon as there are no remaining listeners.
-            Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_SET_TIME);
-            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-            intent.putExtra("time", newSystemClockMillis);
-            mCallback.sendStickyBroadcast(intent);
-        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 94c2192..23b94bd 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -751,10 +751,10 @@
         if (abort) {
             launchObserverNotifyActivityLaunchCancelled(info);
         } else {
-            logAppTransitionFinished(info);
             if (info.isInterestingToLoggerAndObserver()) {
                 launchObserverNotifyActivityLaunchFinished(info, timestampNs);
             }
+            logAppTransitionFinished(info);
         }
         info.mPendingDrawActivities.clear();
         mTransitionInfoList.remove(info);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 65d0c4c..26d76a8d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -243,9 +243,9 @@
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.PipModeChangeItem;
 import android.app.servertransaction.ResumeActivityItem;
+import android.app.servertransaction.StartActivityItem;
 import android.app.servertransaction.StopActivityItem;
 import android.app.servertransaction.TopResumedActivityChangeItem;
-import android.app.servertransaction.WindowVisibilityItem;
 import android.app.usage.UsageEvents.Event;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -4497,7 +4497,8 @@
             sleeping = false;
             app.postPendingUiCleanMsg(true);
             if (reportToClient) {
-                makeClientVisible();
+                mClientVisibilityDeferred = false;
+                makeActiveIfNeeded(starting);
             } else {
                 mClientVisibilityDeferred = true;
             }
@@ -4511,23 +4512,6 @@
         handleAlreadyVisible();
     }
 
-    /** Send visibility change message to the client and pause if needed. */
-    void makeClientVisible() {
-        mClientVisibilityDeferred = false;
-        try {
-            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
-                    WindowVisibilityItem.obtain(true /* showWindow */));
-            makeActiveIfNeeded(null /* activeActivity*/);
-            if (isState(STOPPING, STOPPED)) {
-                // Set state to STARTED in order to have consistent state with client while
-                // making an non-active activity visible from stopped.
-                setState(STARTED, "makeClientVisible");
-            }
-        } catch (Exception e) {
-            Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e);
-        }
-    }
-
     void makeInvisible() {
         if (!mVisibleRequested) {
             if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + this);
@@ -4556,14 +4540,6 @@
             switch (getState()) {
                 case STOPPING:
                 case STOPPED:
-                    if (attachedToProcess()) {
-                        if (DEBUG_VISIBILITY) {
-                            Slog.v(TAG_VISIBILITY, "Scheduling invisibility: " + this);
-                        }
-                        mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(),
-                                appToken, WindowVisibilityItem.obtain(false /* showWindow */));
-                    }
-
                     // Reset the flag indicating that an app can enter picture-in-picture once the
                     // activity is hidden
                     supportsEnterPipOnTaskSwitch = false;
@@ -4595,17 +4571,17 @@
     boolean makeActiveIfNeeded(ActivityRecord activeActivity) {
         if (shouldResumeActivity(activeActivity)) {
             if (DEBUG_VISIBILITY) {
-                Slog.v("TAG_VISIBILITY", "Resume visible activity, " + this);
+                Slog.v(TAG_VISIBILITY, "Resume visible activity, " + this);
             }
             return getActivityStack().resumeTopActivityUncheckedLocked(activeActivity /* prev */,
                     null /* options */);
         } else if (shouldPauseActivity(activeActivity)) {
             if (DEBUG_VISIBILITY) {
-                Slog.v("TAG_VISIBILITY", "Pause visible activity, " + this);
+                Slog.v(TAG_VISIBILITY, "Pause visible activity, " + this);
             }
             // An activity must be in the {@link PAUSING} state for the system to validate
             // the move to {@link PAUSED}.
-            setState(PAUSING, "makeVisibleIfNeeded");
+            setState(PAUSING, "makeActiveIfNeeded");
             try {
                 mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
                         PauseActivityItem.obtain(finishing, false /* userLeaving */,
@@ -4613,6 +4589,17 @@
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
             }
+        } else if (shouldStartActivity()) {
+            if (DEBUG_VISIBILITY) {
+                Slog.v(TAG_VISIBILITY, "Start visible activity, " + this);
+            }
+            setState(STARTED, "makeActiveIfNeeded");
+            try {
+                mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+                        StartActivityItem.obtain());
+            } catch (Exception e) {
+                Slog.w(TAG, "Exception thrown sending start: " + intent.getComponent(), e);
+            }
         }
         return false;
     }
@@ -4656,6 +4643,16 @@
     }
 
     /**
+     * Check if activity should be moved to STARTED state.
+     * NOTE: This will not check if activity should be made paused or resumed first, so it must only
+     * be called after checking with {@link #shouldResumeActivity(ActivityRecord)}
+     * and {@link #shouldPauseActivity(ActivityRecord)}.
+     */
+    private boolean shouldStartActivity() {
+        return mVisibleRequested && isState(STOPPED);
+    }
+
+    /**
      * Check if activity is eligible to be made active (resumed of paused). The activity:
      * - should be paused, stopped or stopping
      * - should not be the currently active one or launching behind other tasks
@@ -4890,16 +4887,13 @@
             }
             setState(STOPPING, "stopIfPossible");
             if (DEBUG_VISIBILITY) {
-                Slog.v(TAG_VISIBILITY, "Stopping visibleRequested="
-                        + mVisibleRequested + " for " + this);
-            }
-            if (!mVisibleRequested) {
-                setVisibility(false);
+                Slog.v(TAG_VISIBILITY, "Stopping:" + this);
             }
             EventLogTags.writeWmStopActivity(
                     mUserId, System.identityHashCode(this), shortComponentName);
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
-                    StopActivityItem.obtain(mVisibleRequested, configChangeFlags));
+                    StopActivityItem.obtain(configChangeFlags));
+
             if (stack.shouldSleepOrShutDownActivities()) {
                 setSleeping(true);
             }
@@ -6107,11 +6101,6 @@
                 // This also avoids if the next activity never reports idle (e.g. animating view),
                 // the previous will need to wait until idle timeout to be stopped or destroyed.
                 mStackSupervisor.scheduleProcessStoppingAndFinishingActivities();
-            } else {
-                // Instead of doing the full stop routine here, let's just hide any activities
-                // we now can, and let them stop when the normal idle happens.
-                mStackSupervisor.processStoppingActivities(null /* launchedActivity */,
-                        true /* onlyUpdateVisibility */, true /* unused */, null /* unused */);
             }
         }
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -7206,7 +7195,7 @@
         // {@link ActivityTaskManagerService.activityStopped}).
         try {
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
-                    StopActivityItem.obtain(false /* showWindow */, 0 /* configChanges */));
+                    StopActivityItem.obtain(0 /* configChanges */));
         } catch (RemoteException e) {
             Slog.w(TAG, "Exception thrown during restart " + this, e);
         }
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index c959439..9fd3ea4 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -163,6 +163,7 @@
 import android.view.DisplayInfo;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
+import android.view.ITaskOrganizer;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -803,6 +804,16 @@
         setWindowingMode(windowingMode, false /* animate */, false /* showRecents */,
                 false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,
                 false /* creating */);
+
+        windowingMode = getWindowingMode();
+        /*
+         * Different windowing modes may be managed by different task organizers. If
+         * getTaskOrganizer returns null, we still call transferToTaskOrganizer to
+         * make sure we clear it.
+         */
+        final ITaskOrganizer org =
+            mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode);
+        transferToTaskOrganizer(org);
     }
 
     /**
@@ -1650,6 +1661,33 @@
     }
 
     /**
+     * Indicate whether the first task in this stack is controlled by a TaskOrganizer. We aren't
+     * expecting to use the TaskOrganizer in multiple task per stack scenarios so checking
+     * the first one is ok.
+     */
+    boolean isControlledByTaskOrganizer() {
+        return getChildCount() > 0 && getTopMostTask().mTaskOrganizer != null;
+    }
+
+    private static void transferSingleTaskToOrganizer(Task tr, ITaskOrganizer organizer) {
+        tr.setTaskOrganizer(organizer);
+    }
+
+    /**
+     * Transfer control of the leashes and IWindowContainers to the given ITaskOrganizer.
+     * This will (or shortly there-after) invoke the taskAppeared callbacks.
+     * If the tasks had a previous TaskOrganizer, setTaskOrganizer will take care of
+     * emitting the taskVanished callbacks.
+     */
+    void transferToTaskOrganizer(ITaskOrganizer organizer) {
+        final PooledConsumer c = PooledLambda.obtainConsumer(
+                ActivityStack::transferSingleTaskToOrganizer,
+                PooledLambda.__(Task.class), organizer);
+        forAllTasks(c);
+        c.recycle();
+    }
+
+    /**
      * Returns true if the stack should be visible.
      *
      * @param starting The currently starting activity or null if there is none.
@@ -3577,6 +3615,15 @@
     void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds, int animationDuration,
             boolean fromFullscreen) {
         if (!inPinnedWindowingMode()) return;
+
+        /**
+         * TODO(b/146594635): Remove all PIP animation code from WM once SysUI handles animation.
+         * If this PIP Task is controlled by a TaskOrganizer, the animation occurs entirely
+         * on the TaskOrganizer side, so we just hand over the leash without doing any animation.
+         * We have to be careful to not schedule the enter-pip callback as the TaskOrganizer
+         * needs to have flexibility to schedule that at an appropriate point in the animation.
+         */
+        if (isControlledByTaskOrganizer()) return;
         if (toBounds == null /* toFullscreen */) {
             final Configuration parentConfig = getParent().getConfiguration();
             final ActivityRecord top = topRunningNonOverlayTaskActivity();
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index e8564fc..aa90248 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -83,6 +83,7 @@
 import static com.android.server.wm.Task.REPARENT_KEEP_STACK_AT_FRONT;
 import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE;
 import static com.android.server.wm.Task.REPARENT_MOVE_STACK_TO_FRONT;
+import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
@@ -2076,54 +2077,11 @@
             boolean processPausingActivities, String reason) {
         // Stop any activities that are scheduled to do so but have been waiting for the transition
         // animation to finish.
-        processStoppingActivities(launchedActivity, false /* onlyUpdateVisibility */,
-                processPausingActivities, reason);
-
-        final int numFinishingActivities = mFinishingActivities.size();
-        if (numFinishingActivities == 0) {
-            return;
-        }
-
-        // Finish any activities that are scheduled to do so but have been waiting for the next one
-        // to start.
-        final ArrayList<ActivityRecord> finishingActivities = new ArrayList<>(mFinishingActivities);
-        mFinishingActivities.clear();
-        for (int i = 0; i < numFinishingActivities; i++) {
-            final ActivityRecord r = finishingActivities.get(i);
-            if (r.isInHistory()) {
-                r.destroyImmediately(true /* removeFromApp */, "finish-" + reason);
-            }
-        }
-    }
-
-    /** Stop or destroy the stopping activities if they are not interactive. */
-    void processStoppingActivities(ActivityRecord launchedActivity, boolean onlyUpdateVisibility,
-            boolean processPausingActivities, String reason) {
-        final int numStoppingActivities = mStoppingActivities.size();
-        if (numStoppingActivities == 0) {
-            return;
-        }
         ArrayList<ActivityRecord> readyToStopActivities = null;
-
-        final boolean nowVisible = mRootWindowContainer.allResumedActivitiesVisible();
-        for (int activityNdx = numStoppingActivities - 1; activityNdx >= 0; --activityNdx) {
-            final ActivityRecord s = mStoppingActivities.get(activityNdx);
-            if (nowVisible && s.finishing) {
-
-                // If this activity is finishing, it is sitting on top of
-                // everyone else but we now know it is no longer needed...
-                // so get rid of it.  Otherwise, we need to go through the
-                // normal flow and hide it once we determine that it is
-                // hidden by the activities in front of it.
-                if (DEBUG_STATES) Slog.v(TAG, "Before stopping, can hide: " + s);
-                s.setVisibility(false);
-            }
-            if (onlyUpdateVisibility) {
-                continue;
-            }
-
-            final boolean animating = s.isAnimating(TRANSITION);
-            if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + nowVisible
+        for (int i = mStoppingActivities.size() - 1; i >= 0; --i) {
+            final ActivityRecord s = mStoppingActivities.get(i);
+            final boolean animating = s.isAnimating(TRANSITION | PARENTS);
+            if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + s.nowVisible
                     + " animating=" + animating + " finishing=" + s.finishing);
 
             final ActivityStack stack = s.getActivityStack();
@@ -2145,7 +2103,7 @@
                 }
                 readyToStopActivities.add(s);
 
-                mStoppingActivities.remove(activityNdx);
+                mStoppingActivities.remove(i);
             }
         }
 
@@ -2161,6 +2119,22 @@
                 }
             }
         }
+
+        final int numFinishingActivities = mFinishingActivities.size();
+        if (numFinishingActivities == 0) {
+            return;
+        }
+
+        // Finish any activities that are scheduled to do so but have been waiting for the next one
+        // to start.
+        final ArrayList<ActivityRecord> finishingActivities = new ArrayList<>(mFinishingActivities);
+        mFinishingActivities.clear();
+        for (int i = 0; i < numFinishingActivities; i++) {
+            final ActivityRecord r = finishingActivities.get(i);
+            if (r.isInHistory()) {
+                r.destroyImmediately(true /* removeFromApp */, "finish-" + reason);
+            }
+        }
     }
 
     void removeHistoryRecords(WindowProcessController app) {
@@ -2574,6 +2548,7 @@
         final PooledConsumer c = PooledLambda.obtainConsumer(
                 ActivityRecord::updatePictureInPictureMode,
                 PooledLambda.__(ActivityRecord.class), targetStackBounds, forceUpdate);
+        task.getStack().setBounds(targetStackBounds);
         task.forAllActivities(c);
         c.recycle();
     }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 76c0e4e..ded603c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -226,6 +226,7 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.view.IRecentsAnimationRunner;
+import android.view.ITaskOrganizer;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
 import android.view.WindowContainerTransaction;
@@ -662,6 +663,12 @@
 
     private FontScaleSettingObserver mFontScaleSettingObserver;
 
+    /**
+     * Stores the registration and state of TaskOrganizers in use.
+     */
+    TaskOrganizerController mTaskOrganizerController =
+        new TaskOrganizerController(this, mGlobalLock);
+
     private int mDeviceOwnerUid = Process.INVALID_UID;
 
     private final class FontScaleSettingObserver extends ContentObserver {
@@ -1271,6 +1278,14 @@
                 .execute();
     }
 
+    @Override
+    public final void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {
+        enforceCallerIsRecentsOrHasPermission(
+                MANAGE_ACTIVITY_STACKS, "registerTaskOrganizer()");
+        synchronized (mGlobalLock) {
+            mTaskOrganizerController.registerTaskOrganizer(organizer, windowingMode);
+        }
+    }
 
     @Override
     public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) {
@@ -3319,6 +3334,18 @@
         }
     }
 
+    private void applyWindowContainerChange(ConfigurationContainer cc,
+            WindowContainerTransaction.Change c) {
+        sanitizeAndApplyConfigChange(cc, c);
+
+        Rect enterPipBounds = c.getEnterPipBounds();
+        if (enterPipBounds != null) {
+            Task tr = (Task) cc;
+            mStackSupervisor.updatePictureInPictureMode(tr,
+                    enterPipBounds, true);
+        }
+    }
+
     @Override
     public void applyContainerTransaction(WindowContainerTransaction t) {
         mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "applyContainerTransaction()");
@@ -3335,7 +3362,7 @@
                             entries.next();
                     final ConfigurationContainer cc = ConfigurationContainer.RemoteToken.fromBinder(
                             entry.getKey()).getContainer();
-                    sanitizeAndApplyConfigChange(cc, entry.getValue());
+                    applyWindowContainerChange(cc, entry.getValue());
                 }
             }
         } finally {
@@ -4057,7 +4084,11 @@
                     throw new IllegalArgumentException("Stack: " + stack
                             + " doesn't support animated resize.");
                 }
-                if (animate) {
+                /**
+                 * TODO(b/146594635): Remove all PIP animation code from WM
+                 * once SysUI handles animation. Don't even try to animate TaskOrganized tasks.
+                 */
+                if (animate && !stack.isControlledByTaskOrganizer()) {
                     stack.animateResizePinnedStack(null /* destBounds */,
                             null /* sourceHintBounds */, animationDuration,
                             false /* fromFullscreen */);
@@ -7203,7 +7234,8 @@
                             ActivityManagerServiceDumpProcessesProto.VR_CONTROLLER);
                     if (mController != null) {
                         final long token = proto.start(CONTROLLER);
-                        proto.write(CONTROLLER, mController.toString());
+                        proto.write(ActivityManagerServiceDumpProcessesProto.Controller.CONTROLLER,
+                                mController.toString());
                         proto.write(IS_A_MONKEY, mControllerIsAMonkey);
                         proto.end(token);
                     }
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 09111d0..014cb76 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -436,10 +436,9 @@
         mNextAppTransition = TRANSIT_UNSET;
         mNextAppTransitionFlags = 0;
         setAppTransitionState(APP_STATE_RUNNING);
-        final AnimationAdapter topOpeningAnim =
-                (topOpeningApp != null && topOpeningApp.getAnimatingContainer() != null)
-                        ? topOpeningApp.getAnimatingContainer().getAnimation()
-                        : null;
+        final WindowContainer wc =
+                topOpeningApp != null ? topOpeningApp.getAnimatingContainer() : null;
+        final AnimationAdapter topOpeningAnim = wc != null ? wc.getAnimation() : null;
 
         int redoLayout = notifyAppTransitionStartingLocked(transit,
                 topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index dd3365c..d0310f1 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -666,4 +666,8 @@
             return sb.toString();
         }
     }
+
+    RemoteToken getRemoteToken() {
+        return mRemoteToken;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 7f94445..ba9d757 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -205,6 +205,7 @@
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 import android.view.Gravity;
+import android.view.IDisplayWindowInsetsController;
 import android.view.ISystemGestureExclusionListener;
 import android.view.IWindow;
 import android.view.InputChannel;
@@ -218,6 +219,7 @@
 import android.view.SurfaceControl.Transaction;
 import android.view.SurfaceSession;
 import android.view.View;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
 
@@ -570,6 +572,8 @@
      */
     WindowState mInputMethodTarget;
 
+    InsetsControlTarget mInputMethodControlTarget;
+
     /** If true hold off on modifying the animation layer of mInputMethodTarget */
     boolean mInputMethodTargetWaitingAnim;
 
@@ -598,6 +602,13 @@
     private final float mWindowCornerRadius;
 
     private final SparseArray<ShellRoot> mShellRoots = new SparseArray<>();
+    RemoteInsetsControlTarget mRemoteInsetsControlTarget = null;
+    private final IBinder.DeathRecipient mRemoteInsetsDeath =
+            () -> {
+                synchronized (mWmService.mGlobalLock) {
+                    mRemoteInsetsControlTarget = null;
+                }
+            };
 
     private RootWindowContainer mRootWindowContainer;
 
@@ -772,7 +783,11 @@
             // If this is the first layout, we need to initialize the last frames and inset values,
             // as otherwise we'd immediately cause an unnecessary resize.
             if (firstLayout) {
-                w.updateLastFrames();
+                // The client may compute its actual requested size according to the first layout,
+                // so we still request the window to resize if the current frame is empty.
+                if (!w.getFrameLw().isEmpty()) {
+                    w.updateLastFrames();
+                }
                 w.updateLastInsetValues();
                 w.updateLocationInParentDisplayIfNeeded();
             }
@@ -1152,6 +1167,22 @@
         mShellRoots.remove(windowType);
     }
 
+    void setRemoteInsetsController(IDisplayWindowInsetsController controller) {
+        if (mRemoteInsetsControlTarget != null) {
+            mRemoteInsetsControlTarget.mRemoteInsetsController.asBinder().unlinkToDeath(
+                    mRemoteInsetsDeath, 0);
+            mRemoteInsetsControlTarget = null;
+        }
+        if (controller != null) {
+            try {
+                controller.asBinder().linkToDeath(mRemoteInsetsDeath, 0);
+                mRemoteInsetsControlTarget = new RemoteInsetsControlTarget(controller);
+            } catch (RemoteException e) {
+                return;
+            }
+        }
+    }
+
     /** Changes the display the input window token is housed on to this one. */
     void reParentWindowToken(WindowToken token) {
         final DisplayContent prevDc = token.getDisplayContent();
@@ -3379,6 +3410,14 @@
         }
     }
 
+    boolean isImeAttachedToApp() {
+        return (mInputMethodTarget != null && mInputMethodTarget.mActivityRecord != null
+                && mInputMethodTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+                // An activity with override bounds should be letterboxed inside its parent bounds,
+                // so it doesn't fill the screen.
+                && mInputMethodTarget.mActivityRecord.matchParentBounds());
+    }
+
     private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) {
         if (target == mInputMethodTarget && mInputMethodTargetWaitingAnim == targetWaitingAnim) {
             return;
@@ -3387,7 +3426,8 @@
         mInputMethodTarget = target;
         mInputMethodTargetWaitingAnim = targetWaitingAnim;
         assignWindowLayers(false /* setLayoutNeeded */);
-        mInsetsStateController.onImeTargetChanged(target);
+        mInputMethodControlTarget = computeImeControlTarget();
+        mInsetsStateController.onImeTargetChanged(mInputMethodControlTarget);
         updateImeParent();
     }
 
@@ -3412,11 +3452,7 @@
         // Attach it to app if the target is part of an app and such app is covering the entire
         // screen. If it's not covering the entire screen the IME might extend beyond the apps
         // bounds.
-        if (mInputMethodTarget != null && mInputMethodTarget.mActivityRecord != null
-                && mInputMethodTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
-                // An activity with override bounds should be letterboxed inside its parent bounds,
-                // so it doesn't fill the screen.
-                && mInputMethodTarget.mActivityRecord.matchParentBounds()) {
+        if (isImeAttachedToApp()) {
             return mInputMethodTarget.mActivityRecord.getSurfaceControl();
         }
 
@@ -3424,6 +3460,19 @@
         return mWindowContainers.getSurfaceControl();
     }
 
+    /**
+     * Computes which control-target the IME should be attached to.
+     */
+    @VisibleForTesting
+    InsetsControlTarget computeImeControlTarget() {
+        if (!isImeAttachedToApp() && mRemoteInsetsControlTarget != null) {
+            return mRemoteInsetsControlTarget;
+        }
+
+        // Otherwise, we just use the ime target
+        return mInputMethodTarget;
+    }
+
     void setLayoutNeeded() {
         if (DEBUG_LAYOUT) Slog.w(TAG_WM, "setLayoutNeeded: callers=" + Debug.getCallers(3));
         mLayoutNeeded = true;
@@ -6684,4 +6733,50 @@
     Context getDisplayUiContext() {
         return mDisplayPolicy.getSystemUiContext();
     }
+
+    class RemoteInsetsControlTarget implements InsetsControlTarget {
+        private final IDisplayWindowInsetsController mRemoteInsetsController;
+
+        RemoteInsetsControlTarget(IDisplayWindowInsetsController controller) {
+            mRemoteInsetsController = controller;
+        }
+
+        void notifyInsetsChanged() {
+            try {
+                mRemoteInsetsController.insetsChanged(
+                        getInsetsStateController().getRawInsetsState());
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to deliver inset state change", e);
+            }
+        }
+
+        @Override
+        public void notifyInsetsControlChanged() {
+            final InsetsStateController stateController = getInsetsStateController();
+            try {
+                mRemoteInsetsController.insetsControlChanged(stateController.getRawInsetsState(),
+                        stateController.getControlsForDispatch(this));
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to deliver inset state change", e);
+            }
+        }
+
+        @Override
+        public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
+            try {
+                mRemoteInsetsController.showInsets(types, fromIme);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to deliver showInsets", e);
+            }
+        }
+
+        @Override
+        public void hideInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
+            try {
+                mRemoteInsetsController.hideInsets(types, fromIme);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to deliver showInsets", e);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index f9ad03f..9c62e99 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2061,7 +2061,8 @@
                         cf.set(displayFrames.mRestricted);
                     }
                     applyStableConstraints(sysUiFl, fl, cf, displayFrames);
-                    if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+                    if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+                            && adjust != SOFT_INPUT_ADJUST_NOTHING) {
                         vf.set(displayFrames.mCurrent);
                     } else {
                         vf.set(cf);
@@ -2138,7 +2139,8 @@
 
                 applyStableConstraints(sysUiFl, fl, cf, displayFrames);
 
-                if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+                if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+                        && adjust != SOFT_INPUT_ADJUST_NOTHING) {
                     vf.set(displayFrames.mCurrent);
                 } else {
                     vf.set(cf);
@@ -2179,7 +2181,8 @@
                         cf.set(displayFrames.mContent);
                         df.set(displayFrames.mContent);
                     }
-                    if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+                    if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+                            && adjust != SOFT_INPUT_ADJUST_NOTHING) {
                         vf.set(displayFrames.mCurrent);
                     } else {
                         vf.set(cf);
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 55f5e28..c09834f 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -79,12 +79,13 @@
 
         final PooledConsumer f = PooledLambda.obtainConsumer(
                 EnsureActivitiesVisibleHelper::setActivityVisibilityState, this,
-                PooledLambda.__(ActivityRecord.class), resumeTopActivity);
+                PooledLambda.__(ActivityRecord.class), starting, resumeTopActivity);
         mContiner.forAllActivities(f);
         f.recycle();
     }
 
-    private void setActivityVisibilityState(ActivityRecord r, final boolean resumeTopActivity) {
+    private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting,
+            final boolean resumeTopActivity) {
         final boolean isTop = r == mTop;
         if (mAboveTop && !isTop) {
             return;
@@ -129,7 +130,8 @@
                         "Skipping: already visible at " + r);
 
                 if (r.mClientVisibilityDeferred && mNotifyClients) {
-                    r.makeClientVisible();
+                    r.makeActiveIfNeeded(r.mClientVisibilityDeferred ? null : starting);
+                    r.mClientVisibilityDeferred = false;
                 }
 
                 r.handleAlreadyVisible();
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 05ede21..fb97ecf 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -69,7 +69,7 @@
         mShowImeRunner = () -> {
             // Target should still be the same.
             if (isImeTargetFromDisplayContentAndImeSame()) {
-                mDisplayContent.mInputMethodTarget.showInsets(
+                mDisplayContent.mInputMethodControlTarget.showInsets(
                         WindowInsets.Type.ime(), true /* fromIme */);
             }
             abortShowImePostLayout();
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index c4b67d7..091f66c 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -279,6 +279,19 @@
         // we avoid reintroducing this concept by just choosing one of them here.
         inputWindowHandle.surfaceInset = child.getAttrs().surfaceInsets.left;
 
+        /**
+         * If the window is in a TaskManaged by a TaskOrganizer then most cropping
+         * will be applied using the SurfaceControl hierarchy from the Organizer.
+         * This means we need to make sure that these changes in crop are reflected
+         * in the input windows, and so ensure this flag is set so that
+         * the input crop always reflects the surface hierarchy.
+         * we may have some issues with modal-windows, but I guess we can
+         * cross that bridge when we come to implementing full-screen TaskOrg
+         */
+        if (child.getTask() != null && child.getTask().isControlledByTaskOrganizer()) {
+            inputWindowHandle.replaceTouchableRegionWithCrop(null /* Use this surfaces crop */);
+        }
+
         if (child.mGlobalScale != 1) {
             // If we are scaling the window, input coordinates need
             // to be inversely scaled to map from what is on screen
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 184e7d6..5a591ec 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -70,6 +70,9 @@
      */
     private boolean mServerVisible;
 
+    private boolean mSeamlessRotating;
+    private long mFinishSeamlessRotateFrameNumber = -1;
+
     private final boolean mControllable;
 
     InsetsSourceProvider(InsetsSource source, InsetsStateController stateController,
@@ -126,6 +129,7 @@
         if (win == null) {
             setServerVisible(false);
             mSource.setFrame(new Rect());
+            mSource.setVisibleFrame(null);
         } else if (mControllable) {
             mWin.setControllableInsetProvider(this);
             if (mControlTarget != null) {
@@ -157,6 +161,15 @@
             mTmpRect.inset(mWin.mGivenContentInsets);
         }
         mSource.setFrame(mTmpRect);
+
+        if (mWin.mGivenVisibleInsets.left != 0 || mWin.mGivenVisibleInsets.top != 0
+                || mWin.mGivenVisibleInsets.right != 0 || mWin.mGivenVisibleInsets.bottom != 0) {
+            mTmpRect.set(mWin.getFrameLw());
+            mTmpRect.inset(mWin.mGivenVisibleInsets);
+            mSource.setVisibleFrame(mTmpRect);
+        } else {
+            mSource.setVisibleFrame(null);
+        }
     }
 
     /**
@@ -170,7 +183,9 @@
         updateSourceFrame();
         if (mControl != null) {
             final Rect frame = mWin.getWindowFrames().mFrame;
-            if (mControl.setSurfacePosition(frame.left, frame.top)) {
+            if (mControl.setSurfacePosition(frame.left, frame.top) && mControlTarget != null) {
+                // The leash has been stale, we need to create a new one for the client.
+                updateControlForTarget(mControlTarget, true /* force */);
                 mStateController.notifyControlChanged(mControlTarget);
             }
         }
@@ -189,6 +204,11 @@
     }
 
     void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) {
+        if (mSeamlessRotating) {
+            // We are un-rotating the window against the display rotation. We don't want the target
+            // to control the window for now.
+            return;
+        }
         if (mWin == null) {
             mControlTarget = target;
             return;
@@ -203,14 +223,42 @@
         }
         mAdapter = new ControlAdapter();
         setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
-        mWin.startAnimation(mDisplayContent.getPendingTransaction(), mAdapter,
-                !mClientVisible /* hidden */);
+        final Transaction t = mDisplayContent.getPendingTransaction();
+        mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */);
+        final SurfaceControl leash = mAdapter.mCapturedLeash;
+        final long frameNumber = mFinishSeamlessRotateFrameNumber;
+        mFinishSeamlessRotateFrameNumber = -1;
+        if (frameNumber >= 0 && mWin.mHasSurface && leash != null) {
+            // We just finished the seamless rotation. We don't want to change the position or the
+            // window crop of the surface controls (including the leash) until the client finishes
+            // drawing the new frame of the new orientation. Although we cannot defer the reparent
+            // operation, it is fine, because reparent won't cause any visual effect.
+            final SurfaceControl barrier = mWin.mWinAnimator.mSurfaceController.mSurfaceControl;
+            t.deferTransactionUntil(mWin.getSurfaceControl(), barrier, frameNumber);
+            t.deferTransactionUntil(leash, barrier, frameNumber);
+        }
         mControlTarget = target;
-        mControl = new InsetsSourceControl(mSource.getType(), mAdapter.mCapturedLeash,
+        mControl = new InsetsSourceControl(mSource.getType(), leash,
                 new Point(mWin.getWindowFrames().mFrame.left, mWin.getWindowFrames().mFrame.top));
     }
 
-    boolean onInsetsModified(WindowState caller, InsetsSource modifiedSource) {
+    void startSeamlessRotation() {
+        if (!mSeamlessRotating) {
+            mSeamlessRotating = true;
+
+            // This will revoke the leash and clear the control target.
+            mWin.cancelAnimation();
+        }
+    }
+
+    void finishSeamlessRotation(boolean timeout) {
+        if (mSeamlessRotating) {
+            mSeamlessRotating = false;
+            mFinishSeamlessRotateFrameNumber = timeout ? -1 : mWin.getFrameNumber();
+        }
+    }
+
+    boolean onInsetsModified(InsetsControlTarget caller, InsetsSource modifiedSource) {
         if (mControlTarget != caller || modifiedSource.isVisible() == mClientVisible) {
             return false;
         }
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 1526074..b2234d1 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -91,6 +91,10 @@
         return state;
     }
 
+    InsetsState getRawInsetsState() {
+        return mState;
+    }
+
     @Nullable InsetsSourceControl[] getControlsForDispatch(InsetsControlTarget target) {
         ArrayList<Integer> controlled = mControlTargetTypeMap.get(target);
         if (controlled == null) {
@@ -144,7 +148,7 @@
         getImeSourceProvider().onPostInsetsDispatched();
     }
 
-    void onInsetsModified(WindowState windowState, InsetsState state) {
+    void onInsetsModified(InsetsControlTarget windowState, InsetsState state) {
         boolean changed = false;
         for (int i = state.getSourcesCount() - 1; i >= 0; i--) {
             final InsetsSource source = state.sourceAt(i);
@@ -199,7 +203,7 @@
         if (target == previous) {
             return;
         }
-        final InsetsSourceProvider provider = getSourceProvider(type);
+        final InsetsSourceProvider provider = mProviders.get(type);
         if (provider == null) {
             return;
         }
@@ -207,6 +211,7 @@
             return;
         }
         provider.updateControlForTarget(target, false /* force */);
+        target = provider.getControlTarget();
         if (previous != null) {
             removeFromControlMaps(previous, type, false /* fake */);
             mPendingControlChanged.add(previous);
@@ -296,6 +301,9 @@
 
     void notifyInsetsChanged() {
         mDisplayContent.forAllWindows(mDispatchInsetsChanged, true /* traverseTopToBottom */);
+        if (mDisplayContent.mRemoteInsetsControlTarget != null) {
+            mDisplayContent.mRemoteInsetsControlTarget.notifyInsetsChanged();
+        }
     }
 
     void dump(String prefix, PrintWriter pw) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a7bf660..c3e815d 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2168,12 +2168,18 @@
             mService.continueWindowLayout();
         }
 
+        // TODO(b/146594635): Remove all PIP animation code from WM once SysUI handles animation.
         // Notify the pinned stack controller to prepare the PiP animation, expect callback
-        // delivered from SystemUI to WM to start the animation.
-        final PinnedStackController pinnedStackController =
+        // delivered from SystemUI to WM to start the animation. Unless we are using
+        // the TaskOrganizer in which case the animation will be entirely handled
+        // on that side.
+        if (mService.mTaskOrganizerController.getTaskOrganizer(WINDOWING_MODE_PINNED)
+                == null) {
+            final PinnedStackController pinnedStackController =
                 display.mDisplayContent.getPinnedStackController();
-        pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio,
-                null /* stackBounds */);
+            pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio,
+                    null /* stackBounds */);
+        }
 
         // TODO: revisit the following statement after the animation is moved from WM to SysUI.
         // Update the visibility of all activities after the they have been reparented to the new
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 976730e..5286a6e 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -322,12 +322,16 @@
         if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to leash");
         final SurfaceControl.Builder builder = mAnimatable.makeAnimationLeash()
                 .setParent(mAnimatable.getAnimationLeashParent())
+                .setHidden(hidden)
                 .setName(surface + " - animation-leash");
         final SurfaceControl leash = builder.build();
         t.setWindowCrop(leash, width, height);
+
+        // TODO: rely on builder.setHidden(hidden) instead of show and setAlpha when b/138459974 is
+        //       fixed.
         t.show(leash);
-        // TODO: change this back to use show instead of alpha when b/138459974 is fixed.
         t.setAlpha(leash, hidden ? 0 : 1);
+
         t.reparent(surface, leash);
         return leash;
     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9a140da..5cb7091 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -117,9 +117,11 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -130,6 +132,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
+import android.view.ITaskOrganizer;
 import android.view.RemoteAnimationTarget;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -425,6 +428,14 @@
     }
 
     /**
+     * The TaskOrganizer which is delegated presentation of this task. If set the Task will
+     * emit an IWindowContainer (allowing access to it's SurfaceControl leash) to the organizers
+     * taskAppeared callback, and emit a taskRemoved callback when the Task is vanished.
+     */
+    ITaskOrganizer mTaskOrganizer;
+
+
+    /**
      * Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int,
      * ActivityInfo, Intent, TaskDescription)} instead.
      */
@@ -445,6 +456,22 @@
                 _voiceSession, _voiceInteractor, stack);
     }
 
+    class TaskToken extends RemoteToken {
+        TaskToken(ConfigurationContainer container) {
+            super(container);
+        }
+
+        @Override
+        public SurfaceControl getLeash() {
+            // We need to copy the SurfaceControl instead of returning the original
+            // because the Parcel FLAGS PARCELABLE_WRITE_RETURN_VALUE cause SurfaceControls
+            // to release themselves.
+            SurfaceControl sc = new SurfaceControl();
+            sc.copyFrom(getSurfaceControl());
+            return sc;
+        }
+    }
+
     /** Don't use constructor directly. This is only used by XML parser. */
     Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
             Intent _affinityIntent, String _affinity, String _rootAffinity,
@@ -469,7 +496,7 @@
         mTaskDescription = _lastTaskDescription;
         // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
         setOrientation(SCREEN_ORIENTATION_UNSET);
-        mRemoteToken = new RemoteToken(this);
+        mRemoteToken = new TaskToken(this);
         affinityIntent = _affinityIntent;
         affinity = _affinity;
         rootAffinity = _rootAffinity;
@@ -2179,6 +2206,10 @@
     void removeImmediately() {
         if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId);
         EventLogTags.writeWmTaskRemoved(mTaskId, "removeTask");
+
+        // If applicable let the TaskOrganizer know the Task is vanishing.
+        setTaskOrganizer(null);
+
         super.removeImmediately();
     }
 
@@ -2567,6 +2598,12 @@
     }
 
     boolean shouldAnimate() {
+        /**
+         * Animations are handled by the TaskOrganizer implementation.
+         */
+        if (isControlledByTaskOrganizer()) {
+            return false;
+        }
         // Don't animate while the task runs recents animation but only if we are in the mode
         // where we cancel with deferred screenshot, which means that the controller has
         // transformed the task.
@@ -3444,4 +3481,91 @@
             XmlUtils.skipCurrentTag(in);
         }
     }
+
+    boolean isControlledByTaskOrganizer() {
+        return mTaskOrganizer != null;
+    }
+
+    @Override
+    protected void reparentSurfaceControl(SurfaceControl.Transaction t, SurfaceControl newParent) {
+        /**
+         * Avoid yanking back control from the TaskOrganizer, which has presumably reparented the
+         * Surface in to its own hierarchy.
+         */
+        if (isControlledByTaskOrganizer()) {
+            return;
+        }
+        super.reparentSurfaceControl(t, newParent);
+    }
+
+    private void sendTaskAppeared() {
+        if (mSurfaceControl != null && mTaskOrganizer != null) {
+            mAtmService.mTaskOrganizerController.onTaskAppeared(mTaskOrganizer, this);
+        }
+    }
+
+    private void sendTaskVanished() {
+        if (mTaskOrganizer != null) {
+            mAtmService.mTaskOrganizerController.onTaskVanished(mTaskOrganizer, this);
+        }
+   }
+
+    void setTaskOrganizer(ITaskOrganizer organizer) {
+        // Let the old organizer know it has lost control.
+        if (mTaskOrganizer != null) {
+            sendTaskVanished();
+        }
+        mTaskOrganizer = organizer;
+        sendTaskAppeared();
+    }
+
+    // Called on Binder death.
+    void taskOrganizerDied() {
+        mTaskOrganizer = null;
+    }
+
+    @Override
+    void setSurfaceControl(SurfaceControl sc) {
+        super.setSurfaceControl(sc);
+        // If the TaskOrganizer was set before we created the SurfaceControl, we need to
+        // emit the callbacks now.
+        sendTaskAppeared();
+    }
+
+    @Override
+    public void updateSurfacePosition() {
+        // Avoid fighting with the TaskOrganizer over Surface position.
+        if (isControlledByTaskOrganizer()) {
+            getPendingTransaction().setPosition(mSurfaceControl, 0, 0);
+            scheduleAnimation();
+            return;
+        } else {
+            super.updateSurfacePosition();
+        }
+    }
+
+    @Override
+    void getRelativeDisplayedPosition(Point outPos) {
+        // In addition to updateSurfacePosition, we keep other code that sets
+        // position from fighting with the TaskOrganizer
+        if (isControlledByTaskOrganizer()) {
+            outPos.set(0, 0);
+            return;
+        }
+        super.getRelativeDisplayedPosition(outPos);
+    }
+
+    @Override
+    public void setWindowingMode(int windowingMode) {
+        super.setWindowingMode(windowingMode);
+        windowingMode = getWindowingMode();
+        /*
+         * Different windowing modes may be managed by different task organizers. If
+         * getTaskOrganizer returns null, we still call transferToTaskOrganizer to
+         * make sure we clear it.
+         */
+        final ITaskOrganizer org =
+            mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode);
+        setTaskOrganizer(org);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
new file mode 100644
index 0000000..283be40
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.ITaskOrganizer;
+import android.view.SurfaceControl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Stores the TaskOrganizers associated with a given windowing mode and
+ * their associated state.
+ */
+class TaskOrganizerController {
+    private static final String TAG = "TaskOrganizerController";
+
+    private WindowManagerGlobalLock mGlobalLock;
+
+    private class DeathRecipient implements IBinder.DeathRecipient {
+        int mWindowingMode;
+        ITaskOrganizer mTaskOrganizer;
+
+        DeathRecipient(ITaskOrganizer organizer, int windowingMode) {
+            mTaskOrganizer = organizer;
+            mWindowingMode = windowingMode;
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mGlobalLock) {
+                final TaskOrganizerState state = mTaskOrganizerStates.get(mTaskOrganizer);
+                for (int i = 0; i < state.mOrganizedTasks.size(); i++) {
+                    state.mOrganizedTasks.get(i).taskOrganizerDied();
+                }
+                mTaskOrganizerStates.remove(mTaskOrganizer);
+                if (mTaskOrganizersForWindowingMode.get(mWindowingMode) == mTaskOrganizer) {
+                    mTaskOrganizersForWindowingMode.remove(mWindowingMode);
+                }
+            }
+        }
+    };
+
+    class TaskOrganizerState {
+        ITaskOrganizer mOrganizer;
+        DeathRecipient mDeathRecipient;
+
+        ArrayList<Task> mOrganizedTasks = new ArrayList<>();
+
+        void addTask(Task t) {
+            mOrganizedTasks.add(t);
+        }
+
+        void removeTask(Task t) {
+            mOrganizedTasks.remove(t);
+        }
+
+        TaskOrganizerState(ITaskOrganizer organizer, DeathRecipient deathRecipient) {
+            mOrganizer = organizer;
+            mDeathRecipient = deathRecipient;
+        }
+    };
+
+
+    final HashMap<Integer, TaskOrganizerState> mTaskOrganizersForWindowingMode = new HashMap();
+    final HashMap<ITaskOrganizer, TaskOrganizerState> mTaskOrganizerStates = new HashMap();
+
+    final HashMap<Integer, ITaskOrganizer> mTaskOrganizersByPendingSyncId = new HashMap();
+
+    final ActivityTaskManagerService mService;
+
+    TaskOrganizerController(ActivityTaskManagerService atm, WindowManagerGlobalLock lock) {
+        mService = atm;
+        mGlobalLock = lock;
+    }
+
+    private void clearIfNeeded(int windowingMode) {
+        final TaskOrganizerState oldState = mTaskOrganizersForWindowingMode.get(windowingMode);
+        if (oldState != null) {
+            oldState.mOrganizer.asBinder().unlinkToDeath(oldState.mDeathRecipient, 0);
+        }
+    }
+
+    /**
+     * Register a TaskOrganizer to manage tasks as they enter the given windowing mode.
+     * If there was already a TaskOrganizer for this windowing mode it will be evicted
+     * and receive taskVanished callbacks in the process.
+     */
+    void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {
+        if (windowingMode != WINDOWING_MODE_PINNED) {
+            throw new UnsupportedOperationException(
+                    "As of now only Pinned windowing mode is supported for registerTaskOrganizer");
+
+        }
+        clearIfNeeded(windowingMode);
+        DeathRecipient dr = new DeathRecipient(organizer, windowingMode);
+        try {
+            organizer.asBinder().linkToDeath(dr, 0);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "TaskOrganizer failed to register death recipient");
+        }
+
+        final TaskOrganizerState state = new TaskOrganizerState(organizer, dr);
+        mTaskOrganizersForWindowingMode.put(windowingMode, state);
+
+        mTaskOrganizerStates.put(organizer, state);
+    }
+
+    ITaskOrganizer getTaskOrganizer(int windowingMode) {
+        final TaskOrganizerState state = mTaskOrganizersForWindowingMode.get(windowingMode);
+        if (state == null) {
+            return null;
+        }
+        return state.mOrganizer;
+    }
+
+    private void sendTaskAppeared(ITaskOrganizer organizer, Task task) {
+        try {
+            organizer.taskAppeared(task.getRemoteToken(), task.getTaskInfo());
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception sending taskAppeared callback" + e);
+        }
+    }
+
+    private void sendTaskVanished(ITaskOrganizer organizer, Task task) {
+        try {
+            organizer.taskVanished(task.getRemoteToken());
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception sending taskVanished callback" + e);
+        }
+    }
+
+    void onTaskAppeared(ITaskOrganizer organizer, Task task) {
+        TaskOrganizerState state = mTaskOrganizerStates.get(organizer);
+
+        state.addTask(task);
+        sendTaskAppeared(organizer, task);
+    }
+
+    void onTaskVanished(ITaskOrganizer organizer, Task task) {
+        final TaskOrganizerState state = mTaskOrganizerStates.get(organizer);
+        sendTaskVanished(organizer, task);
+
+        // This could trigger TaskAppeared for other tasks in the same stack so make sure
+        // we do this AFTER sending taskVanished.
+        state.removeTask(task);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index cefef37..f3880fa 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -342,7 +342,7 @@
         if (mSurfaceControl == null) {
             // If we don't yet have a surface, but we now have a parent, we should
             // build a surface.
-            mSurfaceControl = makeSurface().build();
+            setSurfaceControl(makeSurface().build());
             getPendingTransaction().show(mSurfaceControl);
             updateSurfacePosition();
         } else {
@@ -496,7 +496,7 @@
                 mParent.getPendingTransaction().merge(getPendingTransaction());
             }
 
-            mSurfaceControl = null;
+            setSurfaceControl(null);
             mLastSurfacePosition.set(0, 0);
             scheduleAnimation();
         }
@@ -777,7 +777,7 @@
      *         otherwise.
      */
     boolean isWaitingForTransitionStart() {
-        return getActivity(app -> app.isWaitingForTransitionStart()) != null;
+        return false;
     }
 
     /**
@@ -2209,4 +2209,8 @@
         }
         return mParent.getDimmer();
     }
+
+    void setSurfaceControl(SurfaceControl sc) {
+        mSurfaceControl = sc;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 223e9b9..74fdba1 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -205,6 +205,7 @@
 import android.view.Gravity;
 import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IDisplayFoldListener;
+import android.view.IDisplayWindowInsetsController;
 import android.view.IDisplayWindowListener;
 import android.view.IDisplayWindowRotationController;
 import android.view.IDockedStackListener;
@@ -2768,6 +2769,7 @@
                         true /* includingParents */);
             }
         }
+        syncInputTransactions();
     }
 
     /**
@@ -3727,6 +3729,48 @@
     }
 
     @Override
+    public void setDisplayWindowInsetsController(
+            int displayId, IDisplayWindowInsetsController insetsController) {
+        if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS);
+        }
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                final DisplayContent dc = mRoot.getDisplayContent(displayId);
+                if (dc == null) {
+                    return;
+                }
+                dc.setRemoteInsetsController(insetsController);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public void modifyDisplayWindowInsets(int displayId, InsetsState state) {
+        if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS);
+        }
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                final DisplayContent dc = mRoot.getDisplayContent(displayId);
+                if (dc == null || dc.mRemoteInsetsControlTarget == null) {
+                    return;
+                }
+                dc.getInsetsStateController().onInsetsModified(
+                        dc.mRemoteInsetsControlTarget, state);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
     public int watchRotation(IRotationWatcher watcher, int displayId) {
         final DisplayContent displayContent;
         synchronized (mGlobalLock) {
@@ -7314,7 +7358,8 @@
                     // If there was a pending IME show(), reset it as IME has been
                     // requested to be hidden.
                     dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout();
-                    dc.mInputMethodTarget.hideInsets(WindowInsets.Type.ime(), true /* fromIme */);
+                    dc.mInputMethodControlTarget.hideInsets(WindowInsets.Type.ime(),
+                            true /* fromIme */);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4e768da..ba40f62 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -688,6 +688,9 @@
         }
 
         if (mForceSeamlesslyRotate || requested) {
+            if (mControllableInsetProvider != null) {
+                mControllableInsetProvider.startSeamlessRotation();
+            }
             mPendingSeamlessRotate = new SeamlessRotator(oldRotation, rotation, getDisplayInfo());
             mPendingSeamlessRotate.unrotate(transaction, this);
             getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
@@ -702,6 +705,9 @@
             mPendingSeamlessRotate = null;
             getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
                     false /* seamlesslyRotated */);
+            if (mControllableInsetProvider != null) {
+                mControllableInsetProvider.finishSeamlessRotation(timeout);
+            }
         }
     }
 
@@ -792,8 +798,9 @@
         mSeq = seq;
         mPowerManagerWrapper = powerManagerWrapper;
         mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
-        mRequestedInsetsState =
-                getDisplayContent().getInsetsPolicy().getInsetsForDispatch(this);
+        mRequestedInsetsState = new InsetsState(
+                getDisplayContent().getInsetsPolicy().getInsetsForDispatch(this),
+                true /* copySources */);
         if (DEBUG) {
             Slog.v(TAG, "Window " + this + " client=" + c.asBinder()
                             + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 1ad6e86..03969b0 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -13,6 +13,7 @@
     ],
 
     srcs: [
+        ":graphicsstats_proto",
         "BroadcastRadio/JavaRef.cpp",
         "BroadcastRadio/NativeCallbackThread.cpp",
         "BroadcastRadio/BroadcastRadioService.cpp",
@@ -103,6 +104,11 @@
         "libinputflinger",
         "libinputflinger_base",
         "libinputservice",
+        "libprotobuf-cpp-lite",
+        "libprotoutil",
+        "libstatspull",
+        "libstatssocket",
+        "libstatslog",
         "libschedulerservicehidl",
         "libsensorservice",
         "libsensorservicehidl",
diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp
index d1d253b..9353fbd 100644
--- a/services/core/jni/com_android_server_GraphicsStatsService.cpp
+++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp
@@ -23,6 +23,16 @@
 #include <nativehelper/ScopedUtfChars.h>
 #include <JankTracker.h>
 #include <service/GraphicsStatsService.h>
+#include <stats_pull_atom_callback.h>
+#include <stats_event.h>
+#include <statslog.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <android/util/ProtoOutputStream.h>
+#include "android/graphics/Utils.h"
+#include "core_jni_helpers.h"
+#include "protos/graphicsstats.pb.h"
+#include <cstring>
+#include <memory>
 
 namespace android {
 
@@ -77,6 +87,20 @@
     GraphicsStatsService::finishDump(dump);
 }
 
+static jlong finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr) {
+    GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
+    std::vector<uint8_t>* result = new std::vector<uint8_t>();
+    GraphicsStatsService::finishDumpInMemory(dump,
+        [](void* buffer, int bufferOffset, int bufferSize, int totalSize, void* param1, void* param2) {
+            std::vector<uint8_t>* outBuffer = reinterpret_cast<std::vector<uint8_t>*>(param2);
+            if (outBuffer->size() < totalSize) {
+                outBuffer->resize(totalSize);
+            }
+            std::memcpy(outBuffer->data() + bufferOffset, buffer, bufferSize);
+        }, env, result);
+    return reinterpret_cast<jlong>(result);
+}
+
 static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage,
         jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
     ScopedByteArrayRO buffer(env, jdata);
@@ -93,19 +117,173 @@
     GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data);
 }
 
+static jobject gGraphicsStatsServiceObject = nullptr;
+static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID;
+
+static JNIEnv* getJNIEnv() {
+    JavaVM* vm = AndroidRuntime::getJavaVM();
+    JNIEnv* env = nullptr;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) {
+            LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!");
+        }
+    }
+    return env;
+}
+
+using namespace google::protobuf;
+
+// Field ids taken from FrameTimingHistogram message in atoms.proto
+#define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1
+#define FRAME_COUNTS_FIELD_NUMBER 2
+
+static void writeCpuHistogram(stats_event* event,
+                              const uirenderer::protos::GraphicsStatsProto& stat) {
+    util::ProtoOutputStream proto;
+    for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
+        auto& bucket = stat.histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
+                            TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
+                    (int)bucket.render_millis());
+    }
+    for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
+        auto& bucket = stat.histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
+                            FRAME_COUNTS_FIELD_NUMBER /* field id */,
+                    (long long)bucket.frame_count());
+    }
+    std::vector<uint8_t> outVector;
+    proto.serializeToVector(&outVector);
+    stats_event_write_byte_array(event, outVector.data(), outVector.size());
+}
+
+static void writeGpuHistogram(stats_event* event,
+                              const uirenderer::protos::GraphicsStatsProto& stat) {
+    util::ProtoOutputStream proto;
+    for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
+        auto& bucket = stat.gpu_histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
+                            TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
+                    (int)bucket.render_millis());
+    }
+    for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
+        auto& bucket = stat.gpu_histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
+                            FRAME_COUNTS_FIELD_NUMBER /* field id */,
+                    (long long)bucket.frame_count());
+    }
+    std::vector<uint8_t> outVector;
+    proto.serializeToVector(&outVector);
+    stats_event_write_byte_array(event, outVector.data(), outVector.size());
+}
+
+// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom.
+static bool graphicsStatsPullCallback(int32_t atom_tag, pulled_stats_event_list* data,
+                                      const void* cookie) {
+    JNIEnv* env = getJNIEnv();
+    if (!env) {
+        return false;
+    }
+    if (gGraphicsStatsServiceObject == nullptr) {
+        ALOGE("Failed to get graphicsstats service");
+        return false;
+    }
+
+    for (bool lastFullDay : {true, false}) {
+        jlong jdata = (jlong) env->CallLongMethod(
+                    gGraphicsStatsServiceObject,
+                    gGraphicsStatsService_pullGraphicsStatsMethodID,
+                    (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE));
+        if (env->ExceptionCheck()) {
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+            ALOGE("Failed to invoke graphicsstats service");
+            return false;
+        }
+        if (!jdata) {
+            // null means data is not available for that day.
+            continue;
+        }
+        android::uirenderer::protos::GraphicsStatsServiceDumpProto serviceDump;
+        std::vector<uint8_t>* buffer = reinterpret_cast<std::vector<uint8_t>*>(jdata);
+        std::unique_ptr<std::vector<uint8_t>> bufferRelease(buffer);
+        int dataSize = buffer->size();
+        if (!dataSize) {
+            // Data is not available for that day.
+            continue;
+        }
+        io::ArrayInputStream input{buffer->data(), dataSize};
+        bool success = serviceDump.ParseFromZeroCopyStream(&input);
+        if (!success) {
+            ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'",
+                  serviceDump.InitializationErrorString().c_str(), dataSize);
+            return false;
+        }
+
+        for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) {
+            auto& stat = serviceDump.stats(stat_index);
+            stats_event* event = add_stats_event_to_pull_data(data);
+            stats_event_set_atom_id(event, android::util::GRAPHICS_STATS);
+            stats_event_write_string8(event, stat.package_name().c_str());
+            stats_event_write_int64(event, (int64_t)stat.version_code());
+            stats_event_write_int64(event, (int64_t)stat.stats_start());
+            stats_event_write_int64(event, (int64_t)stat.stats_end());
+            stats_event_write_int32(event, (int32_t)stat.pipeline());
+            stats_event_write_int32(event, (int32_t)stat.summary().total_frames());
+            stats_event_write_int32(event, (int32_t)stat.summary().missed_vsync_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().high_input_latency_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().slow_ui_thread_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().slow_bitmap_upload_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().slow_draw_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().missed_deadline_count());
+            writeCpuHistogram(event, stat);
+            writeGpuHistogram(event, stat);
+            // TODO: fill in UI mainline module version, when the feature is available.
+            stats_event_write_int64(event, (int64_t)0);
+            stats_event_write_bool(event, !lastFullDay);
+            stats_event_build(event);
+        }
+    }
+    return true;
+}
+
+// Register a puller for GRAPHICS_STATS atom with the statsd service.
+static void nativeInit(JNIEnv* env, jobject javaObject) {
+    gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject);
+    pull_atom_metadata metadata = {.cool_down_ns = 10 * 1000000, // 10 milliseconds
+                                   .timeout_ns = 2 * NS_PER_SEC, // 2 seconds
+                                   .additive_fields = nullptr,
+                                   .additive_fields_size = 0};
+    register_stats_pull_atom_callback(android::util::GRAPHICS_STATS, &graphicsStatsPullCallback,
+            &metadata, nullptr);
+}
+
+static void nativeDestructor(JNIEnv* env, jobject javaObject) {
+    //TODO: Unregister the puller callback when a new API is available.
+    env->DeleteGlobalRef(gGraphicsStatsServiceObject);
+    gGraphicsStatsServiceObject = nullptr;
+}
+
 static const JNINativeMethod sMethods[] = {
     { "nGetAshmemSize", "()I", (void*) getAshmemSize },
     { "nCreateDump", "(IZ)J", (void*) createDump },
     { "nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) addToDump },
     { "nAddToDump", "(JLjava/lang/String;)V", (void*) addFileToDump },
     { "nFinishDump", "(J)V", (void*) finishDump },
+    { "nFinishDumpInMemory", "(J)J", (void*) finishDumpInMemory },
     { "nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) saveBuffer },
+    { "nativeInit", "()V", (void*) nativeInit },
+    { "nativeDestructor",   "()V",     (void*)nativeDestructor }
 };
 
 int register_android_server_GraphicsStatsService(JNIEnv* env)
 {
+    jclass graphicsStatsService_class = FindClassOrDie(env,
+            "com/android/server/GraphicsStatsService");
+    gGraphicsStatsService_pullGraphicsStatsMethodID = GetMethodIDOrDie(env,
+            graphicsStatsService_class, "pullGraphicsStats", "(Z)J");
     return jniRegisterNativeMethods(env, "com/android/server/GraphicsStatsService",
                                     sMethods, NELEM(sMethods));
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 2e8e5e7..c0891d7 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1757,6 +1757,12 @@
     return im->getInputManager()->getReader()->canDispatchToDisplay(deviceId, displayId);
 }
 
+static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jclass /* clazz */, jlong ptr) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    im->getInputManager()->getReader()->requestRefreshConfiguration(
+            InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gInputManagerMethods[] = {
@@ -1842,6 +1848,8 @@
             (void*) nativeSetCustomPointerIcon },
     { "nativeCanDispatchToDisplay", "(JII)Z",
             (void*) nativeCanDispatchToDisplay },
+    { "nativeNotifyPortAssociationsChanged", "(J)V",
+            (void*) nativeNotifyPortAssociationsChanged },
 };
 
 #define FIND_CLASS(var, className) \
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 6504e31..acb6bea 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -129,7 +129,6 @@
 using android::hardware::hidl_string;
 using android::hardware::hidl_death_recipient;
 
-using android::hardware::gnss::V1_0::GnssConstellationType;
 using android::hardware::gnss::V1_0::GnssLocationFlags;
 using android::hardware::gnss::V1_0::IAGnssRilCallback;
 using android::hardware::gnss::V1_0::IGnssGeofenceCallback;
@@ -149,6 +148,8 @@
 
 using android::hidl::base::V1_0::IBase;
 
+using GnssConstellationType_V1_0 = android::hardware::gnss::V1_0::GnssConstellationType;
+using GnssConstellationType_V2_0 = android::hardware::gnss::V2_0::GnssConstellationType;
 using GnssLocation_V1_0 = android::hardware::gnss::V1_0::GnssLocation;
 using GnssLocation_V2_0 = android::hardware::gnss::V2_0::GnssLocation;
 using IGnss_V1_0 = android::hardware::gnss::V1_0::IGnss;
@@ -161,6 +162,7 @@
 using IGnssConfiguration_V1_0 = android::hardware::gnss::V1_0::IGnssConfiguration;
 using IGnssConfiguration_V1_1 = android::hardware::gnss::V1_1::IGnssConfiguration;
 using IGnssConfiguration_V2_0 = android::hardware::gnss::V2_0::IGnssConfiguration;
+using IGnssConfiguration_V2_1 = android::hardware::gnss::V2_1::IGnssConfiguration;
 using IGnssDebug_V1_0 = android::hardware::gnss::V1_0::IGnssDebug;
 using IGnssDebug_V2_0 = android::hardware::gnss::V2_0::IGnssDebug;
 using IGnssMeasurement_V1_0 = android::hardware::gnss::V1_0::IGnssMeasurement;
@@ -221,6 +223,7 @@
 sp<IGnssConfiguration_V1_0> gnssConfigurationIface = nullptr;
 sp<IGnssConfiguration_V1_1> gnssConfigurationIface_V1_1 = nullptr;
 sp<IGnssConfiguration_V2_0> gnssConfigurationIface_V2_0 = nullptr;
+sp<IGnssConfiguration_V2_1> gnssConfigurationIface_V2_1 = nullptr;
 sp<IGnssNi> gnssNiIface = nullptr;
 sp<IGnssMeasurement_V1_0> gnssMeasurementIface = nullptr;
 sp<IGnssMeasurement_V1_1> gnssMeasurementIface_V1_1 = nullptr;
@@ -1888,7 +1891,17 @@
         gnssNiIface = gnssNi;
     }
 
-    if (gnssHal_V2_0 != nullptr) {
+    if (gnssHal_V2_1 != nullptr) {
+        auto gnssConfiguration = gnssHal_V2_1->getExtensionGnssConfiguration_2_1();
+        if (!gnssConfiguration.isOk()) {
+            ALOGD("Unable to get a handle to GnssConfiguration_V2_1");
+        } else {
+            gnssConfigurationIface_V2_1 = gnssConfiguration;
+            gnssConfigurationIface_V2_0 = gnssConfigurationIface_V2_1;
+            gnssConfigurationIface_V1_1 = gnssConfigurationIface_V2_1;
+            gnssConfigurationIface = gnssConfigurationIface_V2_1;
+        }
+    } else if (gnssHal_V2_0 != nullptr) {
         auto gnssConfiguration = gnssHal_V2_0->getExtensionGnssConfiguration_2_0();
         if (!gnssConfiguration.isOk()) {
             ALOGD("Unable to get a handle to GnssConfiguration_V2_0");
@@ -1962,7 +1975,11 @@
 static jobject android_location_GnssConfiguration_get_gnss_configuration_version(
         JNIEnv* env, jclass /* jclazz */) {
     jint major, minor;
-    if (gnssConfigurationIface_V2_0 != nullptr) {
+    if (gnssConfigurationIface_V2_1 != nullptr) {
+        major = 2;
+        minor = 1;
+    }
+    else if (gnssConfigurationIface_V2_0 != nullptr) {
         major = 2;
         minor = 0;
     } else if (gnssConfigurationIface_V1_1 != nullptr) {
@@ -2768,7 +2785,7 @@
 
         SingleSatCorrection singleSatCorrection = {
             .singleSatCorrectionFlags = corrFlags,
-            .constellation = static_cast<GnssConstellationType>(constType),
+            .constellation = static_cast<GnssConstellationType_V1_0>(constType),
             .svid = static_cast<uint16_t>(satId),
             .carrierFrequencyHz = carrierFreqHz,
             .probSatIsLos = probSatIsLos,
@@ -2863,8 +2880,8 @@
 static jboolean android_location_GnssConfiguration_set_supl_es(JNIEnv*,
                                                                jobject,
                                                                jint suplEs) {
-    if (gnssConfigurationIface_V2_0 != nullptr) {
-        ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration.hal version 2.0.");
+    if (gnssConfigurationIface_V2_0 != nullptr || gnssConfigurationIface_V2_1 != nullptr) {
+        ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration.hal version 2.0 and higher.");
         return JNI_FALSE;
     }
 
@@ -2892,7 +2909,7 @@
 static jboolean android_location_GnssConfiguration_set_gps_lock(JNIEnv*,
                                                                 jobject,
                                                                 jint gpsLock) {
-    if (gnssConfigurationIface_V2_0 != nullptr) {
+    if (gnssConfigurationIface_V2_0 != nullptr || gnssConfigurationIface_V2_1 != nullptr) {
         ALOGI("Config parameter GPS_LOCK is deprecated in IGnssConfiguration.hal version 2.0.");
         return JNI_FALSE;
     }
@@ -2932,7 +2949,7 @@
 
 static jboolean android_location_GnssConfiguration_set_satellite_blacklist(
         JNIEnv* env, jobject, jintArray constellations, jintArray sv_ids) {
-    if (gnssConfigurationIface_V1_1 == nullptr) {
+    if (gnssConfigurationIface_V1_1 == nullptr && gnssConfigurationIface_V2_1 == nullptr) {
         ALOGI("IGnssConfiguration interface does not support satellite blacklist.");
         return JNI_FALSE;
     }
@@ -2955,11 +2972,24 @@
         return JNI_FALSE;
     }
 
+    if (gnssConfigurationIface_V2_1 != nullptr) {
+        hidl_vec<IGnssConfiguration_V2_1::BlacklistedSource> sources;
+        sources.resize(length);
+
+        for (int i = 0; i < length; i++) {
+            sources[i].constellation = static_cast<GnssConstellationType_V2_0>(constellation_array[i]);
+            sources[i].svid = sv_id_array[i];
+        }
+
+        auto result = gnssConfigurationIface_V2_1->setBlacklist_2_1(sources);
+        return checkHidlReturn(result, "IGnssConfiguration_V2_1 setBlacklist_2_1() failed.");
+    }
+
     hidl_vec<IGnssConfiguration_V1_1::BlacklistedSource> sources;
     sources.resize(length);
 
     for (int i = 0; i < length; i++) {
-        sources[i].constellation = static_cast<GnssConstellationType>(constellation_array[i]);
+        sources[i].constellation = static_cast<GnssConstellationType_V1_0>(constellation_array[i]);
         sources[i].svid = sv_id_array[i];
     }
 
diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp
index 380ee94..cdbe77a 100644
--- a/services/devicepolicy/Android.bp
+++ b/services/devicepolicy/Android.bp
@@ -18,8 +18,3 @@
         "compat-changeid-annotation-processor",
     ],
 }
-
-platform_compat_config {
-    name: "services-devicepolicy-platform-compat-config",
-    src: ":services.devicepolicy",
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 5a1d552..8641059 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -64,4 +64,8 @@
     }
 
     public void setLocationEnabled(ComponentName who, boolean locationEnabled) {}
+
+    public boolean isOrganizationOwnedDeviceWithManagedProfile() {
+        return false;
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d54fc03..6b81fdd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8695,6 +8695,24 @@
     }
 
     @Override
+    public boolean isOrganizationOwnedDeviceWithManagedProfile() {
+        if (!mHasFeature) {
+            return false;
+        }
+        enforceManageUsers();
+
+        return mInjector.binderWithCleanCallingIdentity(() -> {
+            for (UserInfo ui : mUserManager.getUsers()) {
+                if (ui.isManagedProfile() && isProfileOwnerOfOrganizationOwnedDevice(ui.id)) {
+                    return true;
+                }
+            }
+
+            return false;
+        });
+    }
+
+    @Override
     public boolean checkDeviceIdentifierAccess(String packageName, int pid, int uid) {
         ensureCallerIdentityMatchesIfNotSystem(packageName, pid, uid);
 
@@ -11793,6 +11811,11 @@
             return mStateCache;
         }
 
+        @Override
+        public List<String> getAllCrossProfilePackages() {
+            return DevicePolicyManagerService.this.getAllCrossProfilePackages();
+        }
+
     }
 
     private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
@@ -12277,6 +12300,7 @@
                 case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE:
                     return checkManagedProfileProvisioningPreCondition(packageName, callingUserId);
                 case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
+                case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
                     return checkDeviceOwnerProvisioningPreCondition(callingUserId);
                 case DevicePolicyManager.ACTION_PROVISION_MANAGED_USER:
                     return checkManagedUserProvisioningPreCondition(callingUserId);
@@ -14672,8 +14696,10 @@
             for (int i = 0; i < users.length; i++) {
                 final ComponentName componentName = getProfileOwner(users[i]);
                 if (componentName != null) {
-                    admins.add(getActiveAdminForCallerLocked(
-                            componentName, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER));
+                    ActiveAdmin admin = getActiveAdminUncheckedLocked(componentName, users[i]);
+                    if (admin != null) {
+                        admins.add(admin);
+                    }
                 }
             }
             return admins;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index bfec51c..b6a8ca4 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -751,6 +751,7 @@
         // Now that we have the bare essentials of the OS up and running, take
         // note that we just booted, which might send out a rescue party if
         // we're stuck in a runtime restart loop.
+        RescueParty.registerHealthObserver(mSystemContext);
         RescueParty.noteBoot(mSystemContext);
 
         // Manages LEDs and display backlight so we need it to bring up the display.
diff --git a/services/net/java/android/net/ip/IpClientCallbacks.java b/services/net/java/android/net/ip/IpClientCallbacks.java
index 61cd88a..c93e5c5 100644
--- a/services/net/java/android/net/ip/IpClientCallbacks.java
+++ b/services/net/java/android/net/ip/IpClientCallbacks.java
@@ -17,6 +17,7 @@
 package android.net.ip;
 
 import android.net.DhcpResults;
+import android.net.DhcpResultsParcelable;
 import android.net.Layer2PacketParcelable;
 import android.net.LinkProperties;
 
@@ -69,6 +70,18 @@
     public void onNewDhcpResults(DhcpResults dhcpResults) {}
 
     /**
+     * Callback called when new DHCP results are available.
+     *
+     * <p>This is purely advisory and not an indication of provisioning success or failure.  This is
+     * only here for callers that want to expose DHCPv4 results to other APIs
+     * (e.g., WifiInfo#setInetAddress).
+     *
+     * <p>DHCPv4 or static IPv4 configuration failure or success can be determined by whether or not
+     * the passed-in DhcpResults object is null.
+     */
+    public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {}
+
+    /**
      * Indicates that provisioning was successful.
      */
     public void onProvisioningSuccess(LinkProperties newLp) {}
diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java
index 4d60e62..7f723b1 100644
--- a/services/net/java/android/net/ip/IpClientUtil.java
+++ b/services/net/java/android/net/ip/IpClientUtil.java
@@ -119,6 +119,7 @@
         @Override
         public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {
             mCb.onNewDhcpResults(fromStableParcelable(dhcpResults));
+            mCb.onNewDhcpResults(dhcpResults);
         }
 
         @Override
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 8632ca4..8b2f15c 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -81,7 +81,10 @@
 import org.robolectric.shadows.ShadowPackageManager;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.List;
 
 /**
@@ -1238,13 +1241,49 @@
         assertThat(service.getAncestralSerialNumber()).isEqualTo(testSerialNumber2);
     }
 
+    /**
+     * Test that {@link UserBackupManagerService#dump()} for system user does not prefix dump with
+     * "User 0:".
+     */
+    @Test
+    public void testDump_forSystemUser_DoesNotHaveUserPrefix() throws Exception {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        UserBackupManagerService service =
+                BackupManagerServiceTestUtils.createUserBackupManagerServiceAndRunTasks(
+                        UserHandle.USER_SYSTEM,
+                        mContext,
+                        mBackupThread,
+                        mBaseStateDir,
+                        mDataDir,
+                        mTransportManager);
+
+        StringWriter dump = new StringWriter();
+        service.dump(new FileDescriptor(), new PrintWriter(dump), new String[0]);
+
+        assertThat(dump.toString()).startsWith("Backup Manager is ");
+    }
+
+    /**
+     * Test that {@link UserBackupManagerService#dump()} for non-system user prefixes dump with
+     * "User <userid>:".
+     */
+    @Test
+    public void testDump_forNonSystemUser_HasUserPrefix() throws Exception {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        UserBackupManagerService service = createUserBackupManagerServiceAndRunTasks();
+
+        StringWriter dump = new StringWriter();
+        service.dump(new FileDescriptor(), new PrintWriter(dump), new String[0]);
+
+        assertThat(dump.toString()).startsWith("User " + USER_ID + ":" + "Backup Manager is ");
+    }
+
     private File createTestFile() throws IOException {
         File testFile = new File(mContext.getFilesDir(), "test");
         testFile.createNewFile();
         return testFile;
     }
 
-
     /**
      * We can't mock the void method {@link #schedule(Context, long, BackupManagerConstants)} so we
      * extend {@link ShadowKeyValueBackupJob} and throw an exception at the end of the method.
diff --git a/services/robotests/src/com/android/server/location/LocationRequestStatisticsTest.java b/services/robotests/src/com/android/server/location/LocationRequestStatisticsTest.java
new file mode 100644
index 0000000..4cbdbd17
--- /dev/null
+++ b/services/robotests/src/com/android/server/location/LocationRequestStatisticsTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Unit tests for {@link LocationRequestStatistics}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class LocationRequestStatisticsTest {
+
+    /**
+     * Check adding and removing requests & strings
+     */
+    @Test
+    public void testRequestSummary() {
+        LocationRequestStatistics.RequestSummary summary =
+                new LocationRequestStatistics.RequestSummary(
+                "com.example", "gps", 1000);
+        StringWriter stringWriter = new StringWriter();
+        summary.dump(new IndentingPrintWriter(new PrintWriter(stringWriter), "  "), 1234);
+        assertThat(stringWriter.toString()).startsWith("At");
+
+        StringWriter stringWriterRemove = new StringWriter();
+        summary = new LocationRequestStatistics.RequestSummary(
+                "com.example", "gps",
+                LocationRequestStatistics.RequestSummary.REQUEST_ENDED_INTERVAL);
+        summary.dump(new IndentingPrintWriter(new PrintWriter(stringWriterRemove), "  "), 2345);
+        assertThat(stringWriterRemove.toString()).contains("-");
+    }
+
+    /**
+     * Check summary list size capping
+     */
+    @Test
+    public void testSummaryList() {
+        LocationRequestStatistics statistics = new LocationRequestStatistics();
+        statistics.history.addRequest("com.example", "gps", 1000);
+        assertThat(statistics.history.mList.size()).isEqualTo(1);
+        // Try (not) to overflow
+        for (int i = 0; i < LocationRequestStatistics.RequestSummaryLimitedHistory.MAX_SIZE; i++) {
+            statistics.history.addRequest("com.example", "gps", 1000);
+        }
+        assertThat(statistics.history.mList.size()).isEqualTo(
+                LocationRequestStatistics.RequestSummaryLimitedHistory.MAX_SIZE);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 0d6020c..30d89d3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -32,6 +32,7 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.VersionedPackage;
 import android.os.RecoverySystem;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -39,6 +40,8 @@
 import android.provider.Settings;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
+import com.android.server.RescueParty.RescuePartyObserver;
 import com.android.server.am.SettingsToPropertiesMapper;
 import com.android.server.utils.FlagNamespaceUtils;
 
@@ -57,13 +60,15 @@
  * Test RescueParty.
  */
 public class RescuePartyTest {
-    private static final int PERSISTENT_APP_UID = 12;
     private static final long CURRENT_NETWORK_TIME_MILLIS = 0L;
     private static final String FAKE_NATIVE_NAMESPACE1 = "native1";
     private static final String FAKE_NATIVE_NAMESPACE2 = "native2";
     private static final String[] FAKE_RESET_NATIVE_NAMESPACES =
             {FAKE_NATIVE_NAMESPACE1, FAKE_NATIVE_NAMESPACE2};
 
+    private static VersionedPackage sFailingPackage = new VersionedPackage("com.package.name", 1);
+    private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
+
     private MockitoSession mSession;
 
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -182,25 +187,25 @@
 
     @Test
     public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() {
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
+        notePersistentAppCrash();
 
         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
 
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
+        notePersistentAppCrash();
 
         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
 
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
+        notePersistentAppCrash();
 
         verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
 
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
+        notePersistentAppCrash();
 
         verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
         assertEquals(RescueParty.LEVEL_FACTORY_RESET,
@@ -221,20 +226,6 @@
     }
 
     @Test
-    public void testPersistentAppCrashDetectionWithWrongInterval() {
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
-
-        // last persistent app crash is just outside of the boot loop detection window
-        doReturn(CURRENT_NETWORK_TIME_MILLIS
-                + RescueParty.PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS + 1)
-                .when(() -> RescueParty.getElapsedRealtime());
-        notePersistentAppCrash(/*numTimes=*/1);
-
-        assertEquals(RescueParty.LEVEL_NONE,
-                SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
-    }
-
-    @Test
     public void testBootLoopDetectionWithProperInterval() {
         noteBoot(RescueParty.TRIGGER_COUNT - 1);
 
@@ -249,21 +240,6 @@
     }
 
     @Test
-    public void testPersistentAppCrashDetectionWithProperInterval() {
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
-
-        // last persistent app crash is just inside of the boot loop detection window
-        doReturn(CURRENT_NETWORK_TIME_MILLIS
-                + RescueParty.PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS)
-                .when(() -> RescueParty.getElapsedRealtime());
-        notePersistentAppCrash(/*numTimes=*/1);
-
-        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
-        assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
-                SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
-    }
-
-    @Test
     public void testBootLoopDetectionWithWrongTriggerCount() {
         noteBoot(RescueParty.TRIGGER_COUNT - 1);
         assertEquals(RescueParty.LEVEL_NONE,
@@ -271,13 +247,6 @@
     }
 
     @Test
-    public void testPersistentAppCrashDetectionWithWrongTriggerCount() {
-        notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
-        assertEquals(RescueParty.LEVEL_NONE,
-                SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
-    }
-
-    @Test
     public void testIsAttemptingFactoryReset() {
         noteBoot(RescueParty.TRIGGER_COUNT * 4);
 
@@ -319,6 +288,77 @@
                         FAKE_NATIVE_NAMESPACE2, /*makeDefault=*/true));
     }
 
+    @Test
+    public void testExplicitlyEnablingAndDisablingRescue() {
+        SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
+        SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
+        assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), false);
+
+        SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+        assertTrue(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING));
+    }
+
+    @Test
+    public void testHealthCheckLevels() {
+        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+
+        // Ensure that no action is taken for cases where the failure reason is unknown
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_FACTORY_RESET));
+        assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN),
+                PackageHealthObserverImpact.USER_IMPACT_NONE);
+
+        /*
+        For the following cases, ensure that the returned user impact corresponds with the user
+        impact of the next available rescue level, not the current one.
+         */
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_NONE));
+        assertEquals(observer.onHealthCheckFailed(null,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+                PackageHealthObserverImpact.USER_IMPACT_LOW);
+
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS));
+        assertEquals(observer.onHealthCheckFailed(null,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+                PackageHealthObserverImpact.USER_IMPACT_LOW);
+
+
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES));
+        assertEquals(observer.onHealthCheckFailed(null,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+
+
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS));
+        assertEquals(observer.onHealthCheckFailed(null,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+
+
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_FACTORY_RESET));
+        assertEquals(observer.onHealthCheckFailed(null,
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+                PackageHealthObserverImpact.USER_IMPACT_HIGH);
+    }
+
+    @Test
+    public void testRescueLevelIncrementsWhenExecuted() {
+        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+        SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+                RescueParty.LEVEL_NONE));
+        observer.execute(sFailingPackage,
+                PackageWatchdog.FAILURE_REASON_APP_CRASH);
+        assertEquals(SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, -1),
+                RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS);
+    }
+
     private void verifySettingsResets(int resetMode) {
         verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null,
                 resetMode, UserHandle.USER_SYSTEM));
@@ -332,9 +372,8 @@
         }
     }
 
-    private void notePersistentAppCrash(int numTimes) {
-        for (int i = 0; i < numTimes; i++) {
-            RescueParty.noteAppCrash(mMockContext, PERSISTENT_APP_UID);
-        }
+    private void notePersistentAppCrash() {
+        RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage(
+                "com.package.name", 1), PackageWatchdog.FAILURE_REASON_UNKNOWN);
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java
new file mode 100644
index 0000000..06fb102
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.location;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UserInfoStoreTest {
+
+    private static final int USER1_ID = 1;
+    private static final int USER1_MANAGED_ID = 11;
+    private static final int[] USER1_PROFILES = new int[]{USER1_ID, USER1_MANAGED_ID};
+    private static final int USER2_ID = 2;
+    private static final int USER2_MANAGED_ID = 12;
+    private static final int[] USER2_PROFILES = new int[]{USER2_ID, USER2_MANAGED_ID};
+
+    @Mock private Context mContext;
+    @Mock private UserManager mUserManager;
+
+    private StaticMockitoSession mMockingSession;
+    private List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>();
+
+    private UserInfoStore mStore;
+
+    @Before
+    public void setUp() {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .spyStatic(ActivityManager.class)
+                .strictness(Strictness.WARN)
+                .startMocking();
+
+        doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
+        doAnswer(invocation -> {
+            mBroadcastReceivers.add(invocation.getArgument(0));
+            return null;
+        }).when(mContext).registerReceiverAsUser(any(BroadcastReceiver.class), any(
+                UserHandle.class), any(IntentFilter.class), isNull(), any(Handler.class));
+        doReturn(USER1_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER1_ID);
+        doReturn(USER2_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER2_ID);
+        doReturn(new UserInfo(USER1_ID, "", 0)).when(mUserManager).getProfileParent(
+                USER1_MANAGED_ID);
+        doReturn(new UserInfo(USER2_ID, "", 0)).when(mUserManager).getProfileParent(
+                USER2_MANAGED_ID);
+
+        doReturn(USER1_ID).when(ActivityManager::getCurrentUser);
+
+        mStore = new UserInfoStore(mContext);
+        mStore.onSystemReady();
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    private void switchUser(int userId) {
+        doReturn(userId).when(ActivityManager::getCurrentUser);
+        Intent intent = new Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE,
+                userId);
+        for (BroadcastReceiver broadcastReceiver : mBroadcastReceivers) {
+            broadcastReceiver.onReceive(mContext, intent);
+        }
+    }
+
+    @Test
+    public void testListeners() {
+        UserInfoStore.UserChangedListener listener = mock(UserInfoStore.UserChangedListener.class);
+        mStore.addListener(listener);
+
+        switchUser(USER1_ID);
+        verify(listener, never()).onUserChanged(anyInt(), anyInt());
+
+        switchUser(USER2_ID);
+        verify(listener).onUserChanged(USER1_ID, USER2_ID);
+
+        switchUser(USER1_ID);
+        verify(listener).onUserChanged(USER2_ID, USER1_ID);
+    }
+
+    @Test
+    public void testCurrentUser() {
+        assertThat(mStore.getCurrentUserId()).isEqualTo(USER1_ID);
+
+        switchUser(USER2_ID);
+
+        assertThat(mStore.getCurrentUserId()).isEqualTo(USER2_ID);
+
+        switchUser(USER1_ID);
+
+        assertThat(mStore.getCurrentUserId()).isEqualTo(USER1_ID);
+    }
+
+    @Test
+    public void testIsCurrentUserOrProfile() {
+        assertThat(mStore.isCurrentUserOrProfile(USER1_ID)).isTrue();
+        assertThat(mStore.isCurrentUserOrProfile(USER1_MANAGED_ID)).isTrue();
+        assertThat(mStore.isCurrentUserOrProfile(USER2_ID)).isFalse();
+        assertThat(mStore.isCurrentUserOrProfile(USER2_MANAGED_ID)).isFalse();
+
+        switchUser(USER2_ID);
+
+        assertThat(mStore.isCurrentUserOrProfile(USER1_ID)).isFalse();
+        assertThat(mStore.isCurrentUserOrProfile(USER2_ID)).isTrue();
+        assertThat(mStore.isCurrentUserOrProfile(USER1_MANAGED_ID)).isFalse();
+        assertThat(mStore.isCurrentUserOrProfile(USER2_MANAGED_ID)).isTrue();
+    }
+
+    @Test
+    public void testGetParentUserId() {
+        assertThat(mStore.getParentUserId(USER1_ID)).isEqualTo(USER1_ID);
+        assertThat(mStore.getParentUserId(USER1_MANAGED_ID)).isEqualTo(USER1_ID);
+        assertThat(mStore.getParentUserId(USER2_ID)).isEqualTo(USER2_ID);
+        assertThat(mStore.getParentUserId(USER2_MANAGED_ID)).isEqualTo(USER2_ID);
+
+        switchUser(USER2_ID);
+
+        assertThat(mStore.getParentUserId(USER1_ID)).isEqualTo(USER1_ID);
+        assertThat(mStore.getParentUserId(USER2_ID)).isEqualTo(USER2_ID);
+        assertThat(mStore.getParentUserId(USER1_MANAGED_ID)).isEqualTo(USER1_ID);
+        assertThat(mStore.getParentUserId(USER2_MANAGED_ID)).isEqualTo(USER2_ID);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 1829fb7..2fb2021 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
@@ -56,7 +57,6 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.net.wifi.WifiSsid;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -182,8 +182,8 @@
     }
 
     private ScanResult createScanResult(String ssid, String bssid) {
-        ScanResult result = new ScanResult();
-        result.wifiSsid = WifiSsid.createFromAsciiEncoded(ssid);
+        ScanResult result = mock(ScanResult.class);
+        result.SSID = ssid;
         result.BSSID = bssid;
         return result;
     }
@@ -794,7 +794,7 @@
     @Test
     public void testScanResultsScoreCacheFilter_invalidScanResults() throws Exception {
         List<ScanResult> invalidScanResults = Lists.newArrayList(
-                new ScanResult(),
+                mock(ScanResult.class),
                 createScanResult("", SCORED_NETWORK.networkKey.wifiKey.bssid),
                 createScanResult(WifiManager.UNKNOWN_SSID, SCORED_NETWORK.networkKey.wifiKey.bssid),
                 createScanResult(SSID, null),
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index d11d987..c223f13 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -85,6 +85,7 @@
 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
 
 import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.WindowManagerInternal;
 
 import org.junit.Before;
@@ -803,6 +804,11 @@
         }
 
         @Override
+        public boolean switchToInputMethod(String imeId) {
+            return false;
+        }
+
+        @Override
         public boolean isAccessibilityButtonAvailable() throws RemoteException {
             return false;
         }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
new file mode 100644
index 0000000..75239db
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.Manifest;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.LocalServices;
+import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * APCT tests for {@link AccessibilityManagerService}.
+ */
+public class AccessibilityManagerServiceTest extends AndroidTestCase {
+    private static final String TAG = "A11Y_MANAGER_SERVICE_TEST";
+    private static final int ACTION_ID = 20;
+    private static final String LABEL = "label";
+    private static final String INTENT_ACTION = "TESTACTION";
+    private static final String DESCRIPTION = "description";
+    private static final PendingIntent TEST_PENDING_INTENT = PendingIntent.getBroadcast(
+            InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION), 0);
+    private static final RemoteAction TEST_ACTION = new RemoteAction(
+            Icon.createWithContentUri("content://test"),
+            LABEL,
+            DESCRIPTION,
+            TEST_PENDING_INTENT);
+    private static final AccessibilityAction NEW_ACCESSIBILITY_ACTION =
+            new AccessibilityAction(ACTION_ID, LABEL);
+
+    @Mock private PackageManager mMockPackageManager;
+    @Mock private WindowManagerInternal mMockWindowManagerService;
+    @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy;
+    @Mock private SystemActionPerformer mMockSystemActionPerformer;
+    @Mock private AccessibilityWindowManager mMockA11yWindowManager;
+    @Mock private AccessibilityDisplayListener mMockA11yDisplayListener;
+    @Mock private ActivityTaskManagerInternal mMockActivityTaskManagerInternal;
+
+    private AccessibilityManagerService mA11yms;
+
+    @Override
+    protected void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        LocalServices.removeServiceForTest(WindowManagerInternal.class);
+        LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+        LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerService);
+        LocalServices.addService(
+                ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal);
+        mA11yms = new AccessibilityManagerService(
+            InstrumentationRegistry.getContext(),
+            mMockPackageManager,
+            mMockSecurityPolicy,
+            mMockSystemActionPerformer,
+            mMockA11yWindowManager,
+            mMockA11yDisplayListener);
+    }
+
+    @SmallTest
+    public void testRegisterSystemActionWithoutPermission() throws Exception {
+        doThrow(SecurityException.class).when(mMockSecurityPolicy).enforceCallingPermission(
+                Manifest.permission.MANAGE_ACCESSIBILITY,
+                AccessibilityManagerService.FUNCTION_REGISTER_SYSTEM_ACTION);
+
+        try {
+            mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
+            fail();
+        } catch (SecurityException expected) {
+        }
+        verify(mMockSystemActionPerformer, never()).registerSystemAction(ACTION_ID, TEST_ACTION);
+    }
+
+    @SmallTest
+    public void testRegisterSystemAction() throws Exception {
+        mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
+        verify(mMockSystemActionPerformer).registerSystemAction(ACTION_ID, TEST_ACTION);
+    }
+
+    @SmallTest
+    public void testUnregisterSystemActionWithoutPermission() throws Exception {
+        doThrow(SecurityException.class).when(mMockSecurityPolicy).enforceCallingPermission(
+                Manifest.permission.MANAGE_ACCESSIBILITY,
+                AccessibilityManagerService.FUNCTION_UNREGISTER_SYSTEM_ACTION);
+
+        try {
+            mA11yms.unregisterSystemAction(ACTION_ID);
+            fail();
+        } catch (SecurityException expected) {
+        }
+        verify(mMockSystemActionPerformer, never()).unregisterSystemAction(ACTION_ID);
+    }
+
+    @SmallTest
+    public void testUnregisterSystemAction() throws Exception {
+        mA11yms.unregisterSystemAction(ACTION_ID);
+        verify(mMockSystemActionPerformer).unregisterSystemAction(ACTION_ID);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index 99dd9a1..2ce70b6f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -44,6 +44,7 @@
 import android.testing.DexmakerShareClassLoaderRule;
 import android.view.Display;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 67075ed..9db5a08 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -53,6 +53,7 @@
 import android.view.accessibility.IAccessibilityInteractionConnection;
 
 import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
index 44a514f..96ae102e5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
@@ -31,6 +31,8 @@
 import android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java
index de7bc44..30d00ad 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java
@@ -31,6 +31,7 @@
 import android.view.KeyEvent;
 
 import com.android.server.accessibility.FingerprintGestureDispatcher.FingerprintGestureClient;
+import com.android.server.accessibility.test.MessageCapturingHandler;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
index 23ce483..4123556 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
@@ -44,6 +44,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.accessibility.KeyEventDispatcher.KeyEventFilter;
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.policy.WindowManagerPolicy;
 
 import org.hamcrest.Description;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
index 322653b..78e651b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
@@ -33,6 +33,7 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.policy.WindowManagerPolicy;
 
 import org.hamcrest.Description;
@@ -212,4 +213,4 @@
             description.appendText("Matches key event");
         }
     }
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
index 773b877..82c6498 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
@@ -47,6 +47,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.WindowManagerInternal;
 import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
index 36e854c..1ac4a8e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
@@ -54,6 +54,7 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.accessibility.utils.MotionEventMatcher;
 
 import org.hamcrest.Description;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index 8da927d..c8baca6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -37,6 +37,7 @@
 import android.os.IBinder;
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.server.accessibility.test.MessageCapturingHandler;
 import com.android.server.wm.WindowManagerInternal;
 
 import org.junit.After;
diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
index d4182f3..5a1ad86 100644
--- a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
@@ -672,6 +672,30 @@
                 connectionTime2, mKeyStore.getLastConnectionTime(TEST_KEY_2));
     }
 
+    @Test
+    public void testClearAuthorizationsBeforeAdbEnabled() throws Exception {
+        // The adb key store is not instantiated until adb is enabled; however if the user attempts
+        // to clear the adb authorizations when adb is disabled after a boot a NullPointerException
+        // was thrown as deleteKeyStore is invoked against the key store. This test ensures the
+        // key store can be successfully cleared when adb is disabled.
+        mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper());
+
+        clearKeyStore();
+    }
+
+    @Test
+    public void testClearAuthorizationsDeletesKeyFiles() throws Exception {
+        mAdbKeyFile.createNewFile();
+        mAdbKeyXmlFile.createNewFile();
+
+        clearKeyStore();
+
+        assertFalse("The adb key file should have been deleted after revocation of the grants",
+                mAdbKeyFile.exists());
+        assertFalse("The adb xml key file should have been deleted after revocation of the grants",
+                mAdbKeyXmlFile.exists());
+    }
+
     /**
      * Runs an adb test with the provided configuration.
      *
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index f3c76b6..8871348 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -28,6 +28,7 @@
 
 import android.app.admin.DevicePolicyManagerInternal;
 import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetManagerInternal;
 import android.appwidget.AppWidgetProviderInfo;
 import android.appwidget.PendingHostUpdate;
 import android.content.BroadcastReceiver;
@@ -80,6 +81,7 @@
         super.setUp();
         LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
         LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
+        LocalServices.removeServiceForTest(AppWidgetManagerInternal.class);
 
         mTestContext = new TestContext();
         mPkgName = mTestContext.getOpPackageName();
diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
index 2326dfd..3de006c 100644
--- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
@@ -56,9 +57,9 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -83,6 +84,8 @@
     @Mock
     private UserBackupManagerService mUserBackupManagerService;
     @Mock
+    private UserBackupManagerService mNonSystemUserBackupManagerService;
+    @Mock
     private Context mContextMock;
     @Mock
     private PrintWriter mPrintWriterMock;
@@ -105,7 +108,7 @@
 
         mUserServices = new SparseArray<>();
         mUserServices.append(UserHandle.USER_SYSTEM, mUserBackupManagerService);
-        mUserServices.append(NON_USER_SYSTEM, mUserBackupManagerService);
+        mUserServices.append(NON_USER_SYSTEM, mNonSystemUserBackupManagerService);
 
         when(mUserManagerMock.getUserInfo(UserHandle.USER_SYSTEM)).thenReturn(mUserInfoMock);
         when(mUserManagerMock.getUserInfo(NON_USER_SYSTEM)).thenReturn(mUserInfoMock);
@@ -512,19 +515,53 @@
         mService.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]);
 
         verifyNoMoreInteractions(mUserBackupManagerService);
+        verifyNoMoreInteractions(mNonSystemUserBackupManagerService);
+    }
+
+    /**
+     * Test that {@link BackupManagerService#dump()} dumps system user information before non-system
+     * user information.
+     */
+    @Test
+    public void testDump_systemUserFirst() {
+        String[] args = new String[0];
+        mService.dumpWithoutCheckingPermission(mFileDescriptorStub, mPrintWriterMock, args);
+
+        InOrder inOrder =
+                inOrder(mUserBackupManagerService, mNonSystemUserBackupManagerService);
+        inOrder.verify(mUserBackupManagerService)
+                .dump(mFileDescriptorStub, mPrintWriterMock, args);
+        inOrder.verify(mNonSystemUserBackupManagerService)
+                .dump(mFileDescriptorStub, mPrintWriterMock, args);
+        inOrder.verifyNoMoreInteractions();
     }
 
     @Test
-    @Ignore("b/147012496")
-    public void testGetUserForAncestralSerialNumber() {
+    public void testGetUserForAncestralSerialNumber_forSystemUser() {
         BackupManagerServiceTestable.sBackupDisabled = false;
         BackupManagerService backupManagerService =
                 new BackupManagerServiceTestable(mContextMock, mUserServices);
+        when(mUserManagerMock.getProfileIds(UserHandle.getCallingUserId(), false))
+                .thenReturn(new int[] {UserHandle.USER_SYSTEM, NON_USER_SYSTEM});
         when(mUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L);
 
         UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L);
 
-        assertThat(user).isEqualTo(UserHandle.of(1));
+        assertThat(user).isEqualTo(UserHandle.of(UserHandle.USER_SYSTEM));
+    }
+
+    @Test
+    public void testGetUserForAncestralSerialNumber_forNonSystemUser() {
+        BackupManagerServiceTestable.sBackupDisabled = false;
+        BackupManagerService backupManagerService =
+                new BackupManagerServiceTestable(mContextMock, mUserServices);
+        when(mUserManagerMock.getProfileIds(UserHandle.getCallingUserId(), false))
+                .thenReturn(new int[] {UserHandle.USER_SYSTEM, NON_USER_SYSTEM});
+        when(mNonSystemUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L);
+
+        UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L);
+
+        assertThat(user).isEqualTo(UserHandle.of(NON_USER_SYSTEM));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java
new file mode 100644
index 0000000..d0767cc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java
@@ -0,0 +1,58 @@
+/*
+ * 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.compat;
+
+import android.content.pm.ApplicationInfo;
+
+class ApplicationInfoBuilder {
+    private boolean mIsDebuggable;
+    private int mTargetSdk;
+    private String mPackageName;
+
+    private ApplicationInfoBuilder() {
+        mTargetSdk = -1;
+    }
+
+    static ApplicationInfoBuilder create() {
+        return new ApplicationInfoBuilder();
+    }
+
+    ApplicationInfoBuilder withTargetSdk(int targetSdk) {
+        mTargetSdk = targetSdk;
+        return this;
+    }
+
+    ApplicationInfoBuilder debuggable() {
+        mIsDebuggable = true;
+        return this;
+    }
+
+    ApplicationInfoBuilder withPackageName(String packageName) {
+        mPackageName = packageName;
+        return this;
+    }
+
+    ApplicationInfo build() {
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        if (mIsDebuggable) {
+            applicationInfo.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+        }
+        applicationInfo.packageName = mPackageName;
+        applicationInfo.targetSdkVersion = mTargetSdk;
+        return applicationInfo;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
new file mode 100644
index 0000000..328c71d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
@@ -0,0 +1,99 @@
+/*
+ * 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.compat;
+
+import android.content.Context;
+
+import com.android.internal.compat.AndroidBuildClassifier;
+
+import java.util.ArrayList;
+
+/**
+ * Helper class for creating a CompatConfig.
+ */
+class CompatConfigBuilder {
+    private ArrayList<CompatChange> mChanges;
+    private AndroidBuildClassifier mBuildClassifier;
+    private Context mContext;
+
+    private CompatConfigBuilder(AndroidBuildClassifier buildClassifier, Context context) {
+        mChanges = new ArrayList<>();
+        mBuildClassifier = buildClassifier;
+        mContext = context;
+    }
+
+    static CompatConfigBuilder create(AndroidBuildClassifier buildClassifier, Context context) {
+        return new CompatConfigBuilder(buildClassifier, context);
+    }
+
+    CompatConfigBuilder addTargetSdkChangeWithId(int sdk, long id) {
+        mChanges.add(new CompatChange(id, "", sdk, false, ""));
+        return this;
+    }
+
+    CompatConfigBuilder addTargetSdkDisabledChangeWithId(int sdk, long id) {
+        mChanges.add(new CompatChange(id, "", sdk, true, ""));
+        return this;
+    }
+
+    CompatConfigBuilder addTargetSdkChangeWithIdAndName(int sdk, long id, String name) {
+        mChanges.add(new CompatChange(id, name, sdk, false, ""));
+        return this;
+    }
+
+    CompatConfigBuilder addTargetSdkChangeWithIdAndDescription(int sdk, long id,
+            String description) {
+        mChanges.add(new CompatChange(id, "", sdk, false, description));
+        return this;
+    }
+
+    CompatConfigBuilder addEnabledChangeWithId(long id) {
+        mChanges.add(new CompatChange(id, "", -1, false, ""));
+        return this;
+    }
+
+    CompatConfigBuilder addEnabledChangeWithIdAndName(long id, String name) {
+        mChanges.add(new CompatChange(id, name, -1, false, ""));
+        return this;
+    }
+    CompatConfigBuilder addEnabledChangeWithIdAndDescription(long id, String description) {
+        mChanges.add(new CompatChange(id, "", -1, false, description));
+        return this;
+    }
+
+    CompatConfigBuilder addDisabledChangeWithId(long id) {
+        mChanges.add(new CompatChange(id, "", -1, true, ""));
+        return this;
+    }
+
+    CompatConfigBuilder addDisabledChangeWithIdAndName(long id, String name) {
+        mChanges.add(new CompatChange(id, name, -1, true, ""));
+        return this;
+    }
+
+    CompatConfigBuilder addDisabledChangeWithIdAndDescription(long id, String description) {
+        mChanges.add(new CompatChange(id, "", -1, true, description));
+        return this;
+    }
+
+    CompatConfig build() {
+        CompatConfig config = new CompatConfig(mBuildClassifier, mContext);
+        for (CompatChange change : mChanges) {
+            config.addChange(change);
+        }
+        return config;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index cb99c11..407f67e 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -18,12 +18,25 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.compat.AndroidBuildClassifier;
+
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -34,12 +47,12 @@
 @RunWith(AndroidJUnit4.class)
 public class CompatConfigTest {
 
-    private ApplicationInfo makeAppInfo(String pName, int targetSdkVersion) {
-        ApplicationInfo ai = new ApplicationInfo();
-        ai.packageName = pName;
-        ai.targetSdkVersion = targetSdkVersion;
-        return ai;
-    }
+    @Mock
+    private Context mContext;
+    @Mock
+    PackageManager mPackageManager;
+    @Mock
+    private AndroidBuildClassifier mBuildClassifier;
 
     private File createTempDir() {
         String base = System.getProperty("java.io.tmpdir");
@@ -54,112 +67,206 @@
         os.close();
     }
 
-    @Test
-    public void testUnknownChangeEnabled() {
-        CompatConfig pc = new CompatConfig();
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isTrue();
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        // Assume userdebug/eng non-final build
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(true);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(false);
     }
 
     @Test
-    public void testDisabledChangeDisabled() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true, ""));
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse();
+    public void testUnknownChangeEnabled() throws Exception {
+        CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+        assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create().build()))
+            .isTrue();
     }
 
     @Test
-    public void testTargetSdkChangeDisabled() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false, null));
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse();
+    public void testDisabledChangeDisabled() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L)
+                .build();
+
+        assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create().build()))
+            .isFalse();
     }
 
     @Test
-    public void testTargetSdkChangeEnabled() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false, ""));
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue();
+    public void testTargetSdkChangeDisabled() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addTargetSdkChangeWithId(2, 1234L)
+                .build();
+
+        assertThat(compatConfig.isChangeEnabled(1234L,
+            ApplicationInfoBuilder.create().withTargetSdk(2).build()))
+            .isFalse();
     }
 
     @Test
-    public void testDisabledOverrideTargetSdkChange() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true, null));
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isFalse();
+    public void testTargetSdkChangeEnabled() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addTargetSdkChangeWithId(2, 1234L)
+                .build();
+
+        assertThat(compatConfig.isChangeEnabled(1234L,
+            ApplicationInfoBuilder.create().withTargetSdk(3).build())).isTrue();
     }
 
     @Test
-    public void testGetDisabledChanges() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true, null));
-        pc.addChange(new CompatChange(2345L, "OTHER_CHANGE", -1, false, null));
-        assertThat(pc.getDisabledChanges(
-                makeAppInfo("com.some.package", 2))).asList().containsExactly(1234L);
+    public void testDisabledOverrideTargetSdkChange() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addTargetSdkDisabledChangeWithId(2, 1234L)
+                .build();
+
+        assertThat(compatConfig.isChangeEnabled(1234L,
+            ApplicationInfoBuilder.create().withTargetSdk(3).build())).isFalse();
     }
 
     @Test
-    public void testGetDisabledChangesSorted() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true, null));
-        pc.addChange(new CompatChange(123L, "OTHER_CHANGE", 2, true, null));
-        pc.addChange(new CompatChange(12L, "THIRD_CHANGE", 2, true, null));
-        assertThat(pc.getDisabledChanges(
-                makeAppInfo("com.some.package", 2))).asList().containsExactly(12L, 123L, 1234L);
+    public void testGetDisabledChanges() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L)
+                .addEnabledChangeWithId(2345L)
+                .build();
+
+        assertThat(compatConfig.getDisabledChanges(
+            ApplicationInfoBuilder.create().build())).asList().containsExactly(1234L);
     }
 
     @Test
-    public void testPackageOverrideEnabled() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true, null)); // disabled
-        pc.addOverride(1234L, "com.some.package", true);
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isTrue();
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isFalse();
+    public void testGetDisabledChangesSorted() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L)
+                .addDisabledChangeWithId(123L)
+                .addDisabledChangeWithId(12L)
+                .build();
+
+        assertThat(compatConfig.getDisabledChanges(ApplicationInfoBuilder.create().build()))
+            .asList().containsExactly(12L, 123L, 1234L);
     }
 
     @Test
-    public void testPackageOverrideDisabled() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false, null));
-        pc.addOverride(1234L, "com.some.package", false);
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse();
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isTrue();
+    public void testPackageOverrideEnabled() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L)
+                .build();
+
+        compatConfig.addOverride(1234L, "com.some.package", true);
+
+        assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package").build())).isTrue();
+        assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+                .withPackageName("com.other.package").build())).isFalse();
     }
 
     @Test
-    public void testPackageOverrideUnknownPackage() {
-        CompatConfig pc = new CompatConfig();
-        pc.addOverride(1234L, "com.some.package", false);
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse();
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isTrue();
+    public void testPackageOverrideDisabled() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addEnabledChangeWithId(1234L)
+                .build();
+
+        compatConfig.addOverride(1234L, "com.some.package", false);
+
+        assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package").build())).isFalse();
+        assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+                .withPackageName("com.other.package").build())).isTrue();
     }
 
     @Test
-    public void testPackageOverrideUnknownChange() {
-        CompatConfig pc = new CompatConfig();
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isTrue();
+    public void testPackageOverrideUnknownPackage() throws Exception {
+        CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+
+        compatConfig.addOverride(1234L, "com.some.package", false);
+
+        assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package").build())).isFalse();
+        assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+                .withPackageName("com.other.package").build())).isTrue();
     }
 
     @Test
-    public void testRemovePackageOverride() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false, null));
-        pc.addOverride(1234L, "com.some.package", false);
-        pc.removeOverride(1234L, "com.some.package");
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isTrue();
+    public void testPreventAddOverride() throws Exception {
+        final long changeId = 1234L;
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L)
+                .build();
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package")
+                .build();
+        PackageManager packageManager = mock(PackageManager.class);
+        when(mContext.getPackageManager()).thenReturn(packageManager);
+        when(packageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+            .thenReturn(applicationInfo);
+
+        // Force the validator to prevent overriding the change by using a user build.
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+
+        assertThrows(SecurityException.class,
+                () -> compatConfig.addOverride(1234L, "com.some.package", true)
+        );
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
     }
 
     @Test
-    public void testLookupChangeId() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false, null));
-        pc.addChange(new CompatChange(2345L, "ANOTHER_CHANGE", -1, false, null));
-        assertThat(pc.lookupChangeId("MY_CHANGE")).isEqualTo(1234L);
+    public void testPreventRemoveOverride() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L)
+                .build();
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package")
+                .build();
+        when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+            .thenReturn(applicationInfo);
+        // Assume the override was allowed to be added.
+        compatConfig.addOverride(1234L, "com.some.package", true);
+
+        // Validator allows turning on the change.
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
+
+        // Reject all override attempts.
+        // Force the validator to prevent overriding the change by using a user build.
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+        // Try to turn off change, but validator prevents it.
+        assertThrows(SecurityException.class,
+                () -> compatConfig.removeOverride(1234L, "com.some.package"));
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
     }
 
     @Test
-    public void testLookupChangeIdNotPresent() {
-        CompatConfig pc = new CompatConfig();
-        assertThat(pc.lookupChangeId("MY_CHANGE")).isEqualTo(-1L);
+    public void testRemovePackageOverride() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addEnabledChangeWithId(1234L)
+                .build();
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package")
+                .build();
+
+        assertThat(compatConfig.addOverride(1234L, "com.some.package", false)).isTrue();
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
+
+        compatConfig.removeOverride(1234L, "com.some.package");
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
+    }
+
+    @Test
+    public void testLookupChangeId() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addEnabledChangeWithIdAndName(1234L, "MY_CHANGE")
+                .addEnabledChangeWithIdAndName(2345L, "MY_OTHER_CHANGE")
+                .build();
+
+        assertThat(compatConfig.lookupChangeId("MY_CHANGE")).isEqualTo(1234L);
+    }
+
+    @Test
+    public void testLookupChangeIdNotPresent() throws Exception {
+        CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+        assertThat(compatConfig.lookupChangeId("MY_CHANGE")).isEqualTo(-1L);
     }
 
     @Test
@@ -172,14 +279,17 @@
 
         File dir = createTempDir();
         writeToFile(dir, "platform_compat_config.xml", configXml);
+        CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+        compatConfig.initConfigFromLib(dir);
 
-        CompatConfig pc = new CompatConfig();
-        pc.initConfigFromLib(dir);
-
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse();
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue();
-        assertThat(pc.isChangeEnabled(1235L, makeAppInfo("com.some.package", 5))).isFalse();
-        assertThat(pc.isChangeEnabled(1236L, makeAppInfo("com.some.package", 1))).isTrue();
+        assertThat(compatConfig.isChangeEnabled(1234L,
+            ApplicationInfoBuilder.create().withTargetSdk(1).build())).isFalse();
+        assertThat(compatConfig.isChangeEnabled(1234L,
+            ApplicationInfoBuilder.create().withTargetSdk(3).build())).isTrue();
+        assertThat(compatConfig.isChangeEnabled(1235L,
+            ApplicationInfoBuilder.create().withTargetSdk(5).build())).isFalse();
+        assertThat(compatConfig.isChangeEnabled(1236L,
+            ApplicationInfoBuilder.create().withTargetSdk(1).build())).isTrue();
     }
 
     @Test
@@ -195,15 +305,16 @@
         File dir = createTempDir();
         writeToFile(dir, "libcore_platform_compat_config.xml", configXml1);
         writeToFile(dir, "frameworks_platform_compat_config.xml", configXml2);
+        CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+        compatConfig.initConfigFromLib(dir);
 
-        CompatConfig pc = new CompatConfig();
-        pc.initConfigFromLib(dir);
-
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse();
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue();
-        assertThat(pc.isChangeEnabled(1235L, makeAppInfo("com.some.package", 5))).isFalse();
-        assertThat(pc.isChangeEnabled(1236L, makeAppInfo("com.some.package", 1))).isTrue();
+        assertThat(compatConfig.isChangeEnabled(1234L,
+            ApplicationInfoBuilder.create().withTargetSdk(1).build())).isFalse();
+        assertThat(compatConfig.isChangeEnabled(1234L,
+            ApplicationInfoBuilder.create().withTargetSdk(3).build())).isTrue();
+        assertThat(compatConfig.isChangeEnabled(1235L,
+            ApplicationInfoBuilder.create().withTargetSdk(5).build())).isFalse();
+        assertThat(compatConfig.isChangeEnabled(1236L,
+            ApplicationInfoBuilder.create().withTargetSdk(1).build())).isTrue();
     }
 }
-
-
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatibilityChangeConfigBuilder.java b/services/tests/servicestests/src/com/android/server/compat/CompatibilityChangeConfigBuilder.java
new file mode 100644
index 0000000..793296e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatibilityChangeConfigBuilder.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.compat;
+
+import android.compat.Compatibility;
+
+import com.android.internal.compat.CompatibilityChangeConfig;
+
+import java.util.HashSet;
+import java.util.Set;
+
+class CompatibilityChangeConfigBuilder {
+    private Set<Long> mEnabled;
+    private Set<Long> mDisabled;
+
+    private CompatibilityChangeConfigBuilder() {
+        mEnabled = new HashSet<>();
+        mDisabled = new HashSet<>();
+    }
+
+    static CompatibilityChangeConfigBuilder create() {
+        return new CompatibilityChangeConfigBuilder();
+    }
+
+    CompatibilityChangeConfigBuilder enable(Long id) {
+        mEnabled.add(id);
+        return this;
+    }
+
+    CompatibilityChangeConfigBuilder disable(Long id) {
+        mDisabled.add(id);
+        return this;
+    }
+
+    CompatibilityChangeConfig build() {
+        return new CompatibilityChangeConfig(new Compatibility.ChangeConfig(mEnabled, mDisabled));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
new file mode 100644
index 0000000..ecd07bd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
@@ -0,0 +1,383 @@
+/*
+ * 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.compat;
+
+import static com.android.internal.compat.OverrideAllowedState.ALLOWED;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_NON_TARGET_SDK;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_NOT_DEBUGGABLE;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_TARGET_SDK_TOO_HIGH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.compat.AndroidBuildClassifier;
+import com.android.internal.compat.IOverrideValidator;
+import com.android.internal.compat.OverrideAllowedState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class OverrideValidatorImplTest {
+    private static final String PACKAGE_NAME = "my.package";
+    private static final int TARGET_SDK = 10;
+    private static final int TARGET_SDK_BEFORE = 9;
+    private static final int TARGET_SDK_AFTER = 11;
+
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    Context mContext;
+
+    private AndroidBuildClassifier debuggableBuild() {
+        AndroidBuildClassifier buildClassifier = mock(AndroidBuildClassifier.class);
+        when(buildClassifier.isDebuggableBuild()).thenReturn(true);
+        return buildClassifier;
+    }
+
+    private AndroidBuildClassifier betaBuild() {
+        AndroidBuildClassifier buildClassifier = mock(AndroidBuildClassifier.class);
+        when(buildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(buildClassifier.isFinalBuild()).thenReturn(false);
+        return buildClassifier;
+    }
+
+    private AndroidBuildClassifier finalBuild() {
+        AndroidBuildClassifier buildClassifier = mock(AndroidBuildClassifier.class);
+        when(buildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(buildClassifier.isFinalBuild()).thenReturn(true);
+        return buildClassifier;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+    }
+
+    @Test
+    public void getOverrideAllowedState_debugBuildAnyChangeDebugApp_allowOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext)
+                    .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+                    .addTargetSdkChangeWithId(TARGET_SDK, 2)
+                    .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3)
+                    .addEnabledChangeWithId(4)
+                    .addDisabledChangeWithId(5).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .debuggable()
+                        .withTargetSdk(TARGET_SDK)
+                        .withPackageName(PACKAGE_NAME).build());
+
+        OverrideAllowedState stateTargetSdkLessChange =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkEqualChange =
+                overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkAfterChange =
+                overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+        OverrideAllowedState stateEnabledChange =
+                overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME);
+        OverrideAllowedState stateDisabledChange =
+                overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME);
+
+        assertThat(stateTargetSdkLessChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+        assertThat(stateTargetSdkEqualChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+        assertThat(stateTargetSdkAfterChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+        assertThat(stateEnabledChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+        assertThat(stateDisabledChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+    }
+
+    @Test
+    public void getOverrideAllowedState_debugBuildAnyChangeReleaseApp_allowOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext)
+                    .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+                    .addTargetSdkChangeWithId(TARGET_SDK, 2)
+                    .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3)
+                    .addEnabledChangeWithId(4)
+                    .addDisabledChangeWithId(5).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .withTargetSdk(TARGET_SDK).build());
+
+        OverrideAllowedState stateTargetSdkLessChange =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkEqualChange =
+                overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkAfterChange =
+                overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+        OverrideAllowedState stateEnabledChange =
+                overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME);
+        OverrideAllowedState stateDisabledChange =
+                overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME);
+
+        assertThat(stateTargetSdkLessChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+        assertThat(stateTargetSdkEqualChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+        assertThat(stateTargetSdkAfterChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+        assertThat(stateEnabledChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+        assertThat(stateDisabledChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+    }
+
+    @Test
+    public void getOverrideAllowedState_betaBuildTargetSdkChangeDebugApp_allowOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext)
+                        .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+                        .addTargetSdkChangeWithId(TARGET_SDK, 2)
+                        .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .debuggable()
+                        .withTargetSdk(TARGET_SDK)
+                        .withPackageName(PACKAGE_NAME).build());
+
+        OverrideAllowedState stateTargetSdkLessChange =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkEqualChange =
+                overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkAfterChange =
+                overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+
+        assertThat(stateTargetSdkLessChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_BEFORE));
+        assertThat(stateTargetSdkEqualChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK));
+        assertThat(stateTargetSdkAfterChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_AFTER));
+    }
+
+    @Test
+    public void getOverrideAllowedState_betaBuildEnabledChangeDebugApp_rejectOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext)
+                        .addEnabledChangeWithId(1).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .debuggable()
+                        .build());
+
+        OverrideAllowedState allowedState =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+        assertThat(allowedState)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1));
+    }
+
+    @Test
+    public void getOverrideAllowedState_betaBuildDisabledChangeDebugApp_rejectOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext)
+                        .addDisabledChangeWithId(1).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .debuggable()
+                        .withPackageName(PACKAGE_NAME).build());
+
+        OverrideAllowedState allowedState =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+        assertThat(allowedState)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1));
+    }
+
+    @Test
+    public void getOverrideAllowedState_betaBuildAnyChangeReleaseApp_rejectOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext)
+                        .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+                        .addTargetSdkChangeWithId(TARGET_SDK, 2)
+                        .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3)
+                        .addEnabledChangeWithId(4)
+                        .addDisabledChangeWithId(5).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .withTargetSdk(TARGET_SDK).build());
+
+        OverrideAllowedState stateTargetSdkLessChange =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkEqualChange =
+                overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkAfterChange =
+                overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+        OverrideAllowedState stateEnabledChange =
+                overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME);
+        OverrideAllowedState stateDisabledChange =
+                overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME);
+
+        assertThat(stateTargetSdkLessChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateTargetSdkEqualChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateTargetSdkAfterChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateEnabledChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateDisabledChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+    }
+
+    @Test
+    public void getOverrideAllowedState_finalBuildTargetSdkChangeDebugAppOptin_allowOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
+                        .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 1).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .debuggable()
+                        .withTargetSdk(TARGET_SDK)
+                        .withPackageName(PACKAGE_NAME).build());
+
+        OverrideAllowedState allowedState =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+        assertThat(allowedState)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_AFTER));
+    }
+
+    @Test
+    public void getOverrideAllowedState_finalBuildTargetSdkChangeDebugAppOptout_rejectOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
+                        .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+                        .addTargetSdkChangeWithId(TARGET_SDK, 2).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .withTargetSdk(TARGET_SDK)
+                        .debuggable()
+                        .build());
+
+        OverrideAllowedState stateTargetSdkLessChange =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkEqualChange =
+                overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+
+        assertThat(stateTargetSdkLessChange).isEqualTo(
+                new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, TARGET_SDK,
+                                         TARGET_SDK_BEFORE));
+        assertThat(stateTargetSdkEqualChange).isEqualTo(
+                new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, TARGET_SDK, TARGET_SDK));
+    }
+
+    @Test
+    public void getOverrideAllowedState_finalBuildEnabledChangeDebugApp_rejectOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
+                        .addEnabledChangeWithId(1).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .debuggable().build());
+
+        OverrideAllowedState allowedState =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+        assertThat(allowedState)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1));
+    }
+
+    @Test
+    public void getOverrideAllowedState_finalBuildDisabledChangeDebugApp_rejectOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
+                        .addDisabledChangeWithId(1).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .debuggable().build());
+
+        OverrideAllowedState allowedState =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+        assertThat(allowedState)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1));
+    }
+
+    @Test
+    public void getOverrideAllowedState_finalBuildAnyChangeReleaseApp_rejectOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
+                        .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+                        .addTargetSdkChangeWithId(TARGET_SDK, 2)
+                        .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3)
+                        .addEnabledChangeWithId(4)
+                        .addDisabledChangeWithId(5).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .withTargetSdk(TARGET_SDK).build());
+
+        OverrideAllowedState stateTargetSdkLessChange =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkEqualChange =
+                overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkAfterChange =
+                overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+        OverrideAllowedState stateEnabledChange =
+                overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME);
+        OverrideAllowedState stateDisabledChange =
+                overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME);
+
+        assertThat(stateTargetSdkLessChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateTargetSdkEqualChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateTargetSdkAfterChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateEnabledChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateDisabledChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index c406876..ce5d6d9 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -26,21 +26,20 @@
 import static org.mockito.internal.verification.VerificationModeFactory.times;
 import static org.testng.Assert.assertThrows;
 
-import android.compat.Compatibility;
 import android.content.Context;
 import android.content.pm.PackageManager;
 
-import com.android.internal.compat.CompatibilityChangeConfig;
+import androidx.test.runner.AndroidJUnit4;
 
-import com.google.common.collect.ImmutableSet;
+import com.android.internal.compat.AndroidBuildClassifier;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.MockitoAnnotations;
 
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class PlatformCompatTest {
     private static final String PACKAGE_NAME = "my.package";
 
@@ -50,84 +49,77 @@
     private PackageManager mPackageManager;
     @Mock
     CompatChange.ChangeListener mListener1, mListener2;
-
+    PlatformCompat mPlatformCompat;
+    CompatConfig mCompatConfig;
+    @Mock
+    private AndroidBuildClassifier mBuildClassifier;
 
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mPackageManager.getPackageUid(eq(PACKAGE_NAME), eq(0))).thenThrow(
                 new PackageManager.NameNotFoundException());
-        CompatConfig.get().clearChanges();
+        mCompatConfig = new CompatConfig(mBuildClassifier, mContext);
+        mPlatformCompat = new PlatformCompat(mContext, mCompatConfig);
+        // Assume userdebug/eng non-final build
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(true);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(false);
     }
 
     @Test
-    public void testRegisterListenerToSameIdThrows() {
-        PlatformCompat pc = new PlatformCompat(mContext);
-
+    public void testRegisterListenerToSameIdThrows() throws Exception {
         // Registering a listener to change 1 is successful.
-        pc.registerListener(1, mListener1);
+        mPlatformCompat.registerListener(1, mListener1);
         // Registering a listener to change 2 is successful.
-        pc.registerListener(2, mListener1);
+        mPlatformCompat.registerListener(2, mListener1);
         // Trying to register another listener to change id 1 fails.
-        assertThrows(IllegalStateException.class, () -> pc.registerListener(1, mListener1));
+        assertThrows(IllegalStateException.class,
+                () -> mPlatformCompat.registerListener(1, mListener1));
     }
 
     @Test
-    public void testRegisterListenerReturn() {
-        PlatformCompat pc = new PlatformCompat(mContext);
-
-        pc.setOverrides(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of())),
+    public void testRegisterListenerReturn() throws Exception {
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).build(),
                 PACKAGE_NAME);
 
         // Change id 1 is known (added in setOverrides).
-        assertThat(pc.registerListener(1, mListener1)).isTrue();
+        assertThat(mPlatformCompat.registerListener(1, mListener1)).isTrue();
         // Change 2 is unknown.
-        assertThat(pc.registerListener(2, mListener1)).isFalse();
+        assertThat(mPlatformCompat.registerListener(2, mListener1)).isFalse();
     }
 
     @Test
-    public void testListenerCalledOnSetOverrides() {
-        PlatformCompat pc = new PlatformCompat(mContext);
+    public void testListenerCalledOnSetOverrides() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
+        mPlatformCompat.registerListener(2, mListener1);
 
-        pc.registerListener(1, mListener1);
-        pc.registerListener(2, mListener1);
-
-        pc.setOverrides(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
 
         verify(mListener1, times(2)).onCompatChange(PACKAGE_NAME);
     }
 
     @Test
-    public void testListenerNotCalledOnWrongPackage() {
-        PlatformCompat pc = new PlatformCompat(mContext);
+    public void testListenerNotCalledOnWrongPackage() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
+        mPlatformCompat.registerListener(2, mListener1);
 
-        pc.registerListener(1, mListener1);
-        pc.registerListener(2, mListener1);
-
-        pc.setOverridesForTest(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
 
         verify(mListener1, never()).onCompatChange("other.package");
     }
 
     @Test
-    public void testListenerCalledOnSetOverridesTwoListeners() {
-        PlatformCompat pc = new PlatformCompat(mContext);
-        pc.registerListener(1, mListener1);
+    public void testListenerCalledOnSetOverridesTwoListeners() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
 
-        final ImmutableSet<Long> enabled = ImmutableSet.of(1L);
-        final ImmutableSet<Long> disabled = ImmutableSet.of(2L);
-
-        pc.setOverrides(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(enabled, disabled)),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
 
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
@@ -136,11 +128,10 @@
         reset(mListener1);
         reset(mListener2);
 
-        pc.registerListener(2, mListener2);
+        mPlatformCompat.registerListener(2, mListener2);
 
-        pc.setOverrides(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(enabled, disabled)),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
 
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
@@ -148,31 +139,23 @@
     }
 
     @Test
-    public void testListenerCalledOnSetOverridesForTest() {
-        PlatformCompat pc = new PlatformCompat(mContext);
+    public void testListenerCalledOnSetOverridesForTest() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
+        mPlatformCompat.registerListener(2, mListener1);
 
-        pc.registerListener(1, mListener1);
-        pc.registerListener(2, mListener1);
-
-        pc.setOverridesForTest(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
 
         verify(mListener1, times(2)).onCompatChange(PACKAGE_NAME);
     }
 
     @Test
-    public void testListenerCalledOnSetOverridesTwoListenersForTest() {
-        PlatformCompat pc = new PlatformCompat(mContext);
-        pc.registerListener(1, mListener1);
+    public void testListenerCalledOnSetOverridesTwoListenersForTest() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
 
-        final ImmutableSet<Long> enabled = ImmutableSet.of(1L);
-        final ImmutableSet<Long> disabled = ImmutableSet.of(2L);
-
-        pc.setOverridesForTest(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(enabled, disabled)),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
 
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
@@ -181,10 +164,10 @@
         reset(mListener1);
         reset(mListener2);
 
-        pc.registerListener(2, mListener2);
-        pc.setOverridesForTest(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(enabled, disabled)),
+        mPlatformCompat.registerListener(2, mListener2);
+
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
 
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
@@ -192,15 +175,12 @@
     }
 
     @Test
-    public void testListenerCalledOnClearOverrides() {
-        PlatformCompat pc = new PlatformCompat(mContext);
+    public void testListenerCalledOnClearOverrides() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
+        mPlatformCompat.registerListener(2, mListener2);
 
-        pc.registerListener(1, mListener1);
-        pc.registerListener(2, mListener2);
-
-        pc.setOverrides(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of())),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).build(),
                 PACKAGE_NAME);
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
         verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
@@ -208,21 +188,18 @@
         reset(mListener1);
         reset(mListener2);
 
-        pc.clearOverrides(PACKAGE_NAME);
+        mPlatformCompat.clearOverrides(PACKAGE_NAME);
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
         verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
     }
 
     @Test
-    public void testListenerCalledOnClearOverridesMultipleOverrides() {
-        PlatformCompat pc = new PlatformCompat(mContext);
+    public void testListenerCalledOnClearOverridesMultipleOverrides() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
+        mPlatformCompat.registerListener(2, mListener2);
 
-        pc.registerListener(1, mListener1);
-        pc.registerListener(2, mListener2);
-
-        pc.setOverrides(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
         verify(mListener2, times(1)).onCompatChange(PACKAGE_NAME);
@@ -230,21 +207,18 @@
         reset(mListener1);
         reset(mListener2);
 
-        pc.clearOverrides(PACKAGE_NAME);
+        mPlatformCompat.clearOverrides(PACKAGE_NAME);
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
         verify(mListener2, times(1)).onCompatChange(PACKAGE_NAME);
     }
 
     @Test
-    public void testListenerCalledOnClearOverrideExists() {
-        PlatformCompat pc = new PlatformCompat(mContext);
+    public void testListenerCalledOnClearOverrideExists() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
+        mPlatformCompat.registerListener(2, mListener2);
 
-        pc.registerListener(1, mListener1);
-        pc.registerListener(2, mListener2);
-
-        pc.setOverrides(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of())),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).build(),
                 PACKAGE_NAME);
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
         verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
@@ -252,21 +226,17 @@
         reset(mListener1);
         reset(mListener2);
 
-        pc.clearOverride(1, PACKAGE_NAME);
+        mPlatformCompat.clearOverride(1, PACKAGE_NAME);
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
         verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
     }
 
     @Test
-    public void testListenerCalledOnClearOverrideDoesntExist() {
-        PlatformCompat pc = new PlatformCompat(mContext);
+    public void testListenerCalledOnClearOverrideDoesntExist() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
 
-        pc.registerListener(1, mListener1);
-
-        pc.clearOverride(1, PACKAGE_NAME);
+        mPlatformCompat.clearOverride(1, PACKAGE_NAME);
         // Listener not called when a non existing override is removed.
         verify(mListener1, never()).onCompatChange(PACKAGE_NAME);
     }
-
-
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 6d68446..21034d3 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -2958,6 +2958,7 @@
         mContext.packageName = admin1.getPackageName();
         setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, false);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
                 false);
@@ -2969,6 +2970,8 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
         assertCheckProvisioningPreCondition(
@@ -2995,6 +2998,7 @@
         mContext.packageName = admin1.getPackageName();
         setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
                 false);
@@ -3003,6 +3007,7 @@
         // Test again when split user is on
         when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
                 true);
@@ -3014,6 +3019,8 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_OK);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED);
         assertCheckProvisioningPreCondition(
@@ -3026,6 +3033,8 @@
         when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_OK);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED);
         assertCheckProvisioningPreCondition(
@@ -3051,6 +3060,7 @@
         mContext.packageName = admin1.getPackageName();
         setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
                 false /* because of non-split user */);
@@ -3064,6 +3074,8 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_OK);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(
@@ -3108,6 +3120,8 @@
         setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 false/* because of completed device setup */);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                false/* because of completed device setup */);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
                 false/* because of non-split user */);
@@ -3121,6 +3135,8 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_USER_SETUP_COMPLETED);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_USER_SETUP_COMPLETED);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(
@@ -3137,7 +3153,10 @@
 
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, false);
 
         // COMP mode is allowed.
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
@@ -3266,6 +3285,7 @@
         mContext.packageName = admin1.getPackageName();
         setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 false /* because canAddMoreManagedProfiles returns false */);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
@@ -3280,6 +3300,8 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_OK);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER);
         assertCheckProvisioningPreCondition(
@@ -3306,6 +3328,8 @@
         setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 true/* it's undefined behavior. Can be changed into false in the future */);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                true/* it's undefined behavior. Can be changed into false in the future */);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 false /* because canAddMoreManagedProfiles returns false */);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
@@ -3320,6 +3344,8 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_OK);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER);
         assertCheckProvisioningPreCondition(
@@ -3345,6 +3371,7 @@
         mContext.packageName = admin1.getPackageName();
         setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
                 true);
@@ -3357,6 +3384,8 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_OK);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(
@@ -3384,6 +3413,8 @@
         setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 true/* it's undefined behavior. Can be changed into false in the future */);
+        assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                true/* it's undefined behavior. Can be changed into false in the future */);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
         assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
                 true/* it's undefined behavior. Can be changed into false in the future */);
@@ -3397,6 +3428,8 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
                 DevicePolicyManager.CODE_OK);
+        assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+                DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
                 DevicePolicyManager.CODE_OK);
         assertCheckProvisioningPreCondition(
@@ -3749,6 +3782,29 @@
         verify(getServices().settings).settingsGlobalPutInt(Settings.Global.AUTO_TIME_ZONE, 0);
     }
 
+    public void testIsOrganizationOwnedDevice() throws Exception {
+        setupProfileOwner();
+        // Set up the user manager to return correct user info
+        UserInfo managedProfileUserInfo = new UserInfo(DpmMockContext.CALLER_USER_HANDLE,
+                "managed profile",
+                UserInfo.FLAG_MANAGED_PROFILE);
+        when(getServices().userManager.getUsers())
+                .thenReturn(Arrays.asList(managedProfileUserInfo));
+
+        // Any caller without the MANAGE_USERS permission should get a security exception.
+        assertExpectException(SecurityException.class, null, () ->
+                dpm.isOrganizationOwnedDeviceWithManagedProfile());
+        // But when the right permission is granted, this should succeed.
+        mContext.permissions.add(android.Manifest.permission.MANAGE_USERS);
+        assertFalse(dpm.isOrganizationOwnedDeviceWithManagedProfile());
+        configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+        assertTrue(dpm.isOrganizationOwnedDeviceWithManagedProfile());
+
+        // A random caller from another user should also be able to get the right result.
+        mContext.binder.callingUid = DpmMockContext.ANOTHER_UID;
+        assertTrue(dpm.isOrganizationOwnedDeviceWithManagedProfile());
+    }
+
     public void testSetTime() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index ebca240..25d0778 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -78,7 +78,7 @@
         int displayId = 0;
 
         // With no votes present, DisplayModeDirector should allow any refresh rate.
-        assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*defaultModeId=*/60,
+        assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*baseModeId=*/60,
                              new DisplayModeDirector.RefreshRateRange(0f, Float.POSITIVE_INFINITY)),
                 createDisplayModeDirectorWithDisplayFpsRange(60, 90).getDesiredDisplayModeSpecs(
                         displayId));
@@ -105,7 +105,7 @@
                 director.injectVotesByDisplay(votesByDisplay);
                 assertEquals(
                         new DisplayModeDirector.DesiredDisplayModeSpecs(
-                                /*defaultModeId=*/minFps + i,
+                                /*baseModeId=*/minFps + i,
                                 new DisplayModeDirector.RefreshRateRange(minFps + i, maxFps - i)),
                         director.getDesiredDisplayModeSpecs(displayId));
             }
@@ -126,7 +126,7 @@
             votes.put(DisplayModeDirector.Vote.MIN_PRIORITY,
                     DisplayModeDirector.Vote.forRefreshRates(70, 80));
             director.injectVotesByDisplay(votesByDisplay);
-            assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*defaultModeId=*/70,
+            assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*baseModeId=*/70,
                                  new DisplayModeDirector.RefreshRateRange(70, 80)),
                     director.getDesiredDisplayModeSpecs(displayId));
         }
diff --git a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
index 63189e7..dd69c66 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
@@ -16,14 +16,7 @@
 
 package com.android.server.integrity;
 
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.hasItems;
-import static org.junit.Assert.assertThat;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
 
 import android.content.integrity.AppInstallMetadata;
 import android.content.integrity.AtomicFormula;
@@ -33,26 +26,26 @@
 import android.content.integrity.Rule;
 import android.util.Slog;
 
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.integrity.parser.RuleXmlParser;
-import com.android.server.integrity.serializer.RuleXmlSerializer;
+import com.android.server.integrity.parser.RuleBinaryParser;
+import com.android.server.integrity.serializer.RuleBinarySerializer;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
 import java.io.File;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 
 /** Unit test for {@link IntegrityFileManager} */
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnit4.class)
 public class IntegrityFileManagerTest {
     private static final String TAG = "IntegrityFileManagerTest";
 
@@ -72,7 +65,7 @@
         // Use Xml Parser/Serializer to help with debugging since we can just print the file.
         mIntegrityFileManager =
                 new IntegrityFileManager(
-                        new RuleXmlParser(), new RuleXmlSerializer(), mTmpDir);
+                        new RuleBinaryParser(), new RuleBinarySerializer(), mTmpDir);
         Files.walk(mTmpDir.toPath())
                 .forEach(
                         path -> {
@@ -97,12 +90,19 @@
 
     @Test
     public void testGetMetadata() throws Exception {
-        assertNull(mIntegrityFileManager.readMetadata());
+        assertThat(mIntegrityFileManager.readMetadata()).isNull();
         mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
 
-        assertNotNull(mIntegrityFileManager.readMetadata());
-        assertEquals(VERSION, mIntegrityFileManager.readMetadata().getVersion());
-        assertEquals(RULE_PROVIDER, mIntegrityFileManager.readMetadata().getRuleProvider());
+        assertThat(mIntegrityFileManager.readMetadata()).isNotNull();
+        assertThat(mIntegrityFileManager.readMetadata().getVersion()).isEqualTo(VERSION);
+        assertThat(mIntegrityFileManager.readMetadata().getRuleProvider()).isEqualTo(RULE_PROVIDER);
+    }
+
+    @Test
+    public void testIsInitialized() throws Exception {
+        assertThat(mIntegrityFileManager.initialized()).isFalse();
+        mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
+        assertThat(mIntegrityFileManager.initialized()).isTrue();
     }
 
     @Test
@@ -110,20 +110,8 @@
         String packageName = "package";
         String packageCert = "cert";
         int version = 123;
-        Rule packageNameRule =
-                new Rule(
-                        new StringAtomicFormula(
-                                AtomicFormula.PACKAGE_NAME,
-                                packageName,
-                                /* isHashedValue= */ false),
-                        Rule.DENY);
-        Rule packageCertRule =
-                new Rule(
-                        new StringAtomicFormula(
-                                AtomicFormula.APP_CERTIFICATE,
-                                packageCert,
-                                /* isHashedValue= */ false),
-                        Rule.DENY);
+        Rule packageNameRule = getPackageNameIndexedRule(packageName);
+        Rule packageCertRule = getAppCertificateIndexedRule(packageCert);
         Rule versionCodeRule =
                 new Rule(
                         new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LE, version),
@@ -142,9 +130,7 @@
                                                 AtomicFormula.LE,
                                                 version))),
                         Rule.DENY);
-        // We will test the specifics of indexing in other classes. Here, we just require that all
-        // rules that are related to the given AppInstallMetadata are returned and do not assert
-        // anything on other rules.
+
         List<Rule> rules =
                 Arrays.asList(packageNameRule, packageCertRule, versionCodeRule, randomRule);
         mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules);
@@ -159,17 +145,77 @@
                 .build();
         List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata);
 
-        assertThat(rulesFetched, hasItems(
-                equalTo(packageNameRule),
-                equalTo(packageCertRule),
-                equalTo(versionCodeRule)
-        ));
+        assertThat(rulesFetched)
+                .containsExactly(packageNameRule, packageCertRule, versionCodeRule, randomRule);
     }
 
     @Test
-    public void testIsInitialized() throws Exception {
-        assertFalse(mIntegrityFileManager.initialized());
-        mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
-        assertTrue(mIntegrityFileManager.initialized());
+    public void testGetRules_indexedForManyRules() throws Exception {
+        String packageName = "package";
+        String installerName = "installer";
+        String appCertificate = "cert";
+
+        // Create a rule set with 2500 package name indexed, 2500 app certificate indexed and
+        // 500 unindexed rules.
+        List<Rule> rules = new ArrayList<>();
+
+        for (int i = 0; i < 2500; i++) {
+            rules.add(getPackageNameIndexedRule(String.format("%s%04d", packageName, i)));
+            rules.add(getAppCertificateIndexedRule(String.format("%s%04d", appCertificate, i)));
+        }
+
+        for (int i = 0; i < 70; i++) {
+            rules.add(getInstallerCertificateRule(String.format("%s%04d", installerName, i)));
+        }
+
+        // Write the rules and get them indexed.
+        mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules);
+
+        // Read the rules for a specific rule.
+        String installedPackageName = String.format("%s%04d", packageName, 264);
+        String installedAppCertificate = String.format("%s%04d", appCertificate, 1264);
+        AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder()
+                .setPackageName(installedPackageName)
+                .setAppCertificate(installedAppCertificate)
+                .setVersionCode(250)
+                .setInstallerName("abc")
+                .setInstallerCertificate("abc")
+                .setIsPreInstalled(true)
+                .build();
+        List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata);
+
+        // Verify that we do not load all the rules and we have the necessary rules to evaluate.
+        assertThat(rulesFetched.size()).isEqualTo(170);
+        assertThat(rulesFetched)
+                .containsAllOf(
+                        getPackageNameIndexedRule(installedPackageName),
+                        getAppCertificateIndexedRule(installedAppCertificate));
+    }
+
+    private Rule getPackageNameIndexedRule(String packageName) {
+        return new Rule(
+                new StringAtomicFormula(
+                        AtomicFormula.PACKAGE_NAME,
+                        packageName,
+                        /* isHashedValue= */ false),
+                Rule.DENY);
+    }
+
+    private Rule getAppCertificateIndexedRule(String appCertificate) {
+        return new Rule(
+                new StringAtomicFormula(
+                        AtomicFormula.APP_CERTIFICATE,
+                        appCertificate,
+                        /* isHashedValue= */ false),
+                Rule.DENY);
+    }
+
+    private Rule getInstallerCertificateRule(String installerCert) {
+        return new Rule(
+                new StringAtomicFormula(
+                        AtomicFormula.INSTALLER_NAME,
+                        installerCert,
+                        /* isHashedValue= */ false),
+                Rule.DENY);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java
new file mode 100644
index 0000000..1eb5eb5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/integrity/model/BitTrackedInputStreamTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.model;
+
+import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
+import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
+import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
+import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
+import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS;
+import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS;
+import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
+import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
+import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
+import static com.android.server.integrity.utils.TestUtils.getBits;
+import static com.android.server.integrity.utils.TestUtils.getBytes;
+import static com.android.server.integrity.utils.TestUtils.getValueBits;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.integrity.AtomicFormula;
+import android.content.integrity.CompoundFormula;
+import android.content.integrity.Rule;
+
+import com.android.server.integrity.parser.BinaryFileOperations;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+
+@RunWith(JUnit4.class)
+public class BitTrackedInputStreamTest {
+    private static final String COMPOUND_FORMULA_START_BITS =
+            getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS);
+    private static final String COMPOUND_FORMULA_END_BITS =
+            getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS);
+    private static final String ATOMIC_FORMULA_START_BITS =
+            getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS);
+    private static final String NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS);
+    private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS);
+    private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS);
+    private static final String DENY = getBits(Rule.DENY, EFFECT_BITS);
+
+    private static final String IS_NOT_HASHED = "0";
+    private static final String START_BIT = "1";
+    private static final String END_BIT = "1";
+
+    @Test
+    public void testBitOperationsCountBitsCorrectly() throws IOException {
+        String packageName = "com.test.app";
+        byte[] testInput =
+                getBytes(
+                        START_BIT
+                                + COMPOUND_FORMULA_START_BITS
+                                + NOT
+                                + ATOMIC_FORMULA_START_BITS
+                                + PACKAGE_NAME
+                                + EQ
+                                + IS_NOT_HASHED
+                                + getBits(packageName.length(), VALUE_SIZE_BITS)
+                                + getValueBits(packageName)
+                                + COMPOUND_FORMULA_END_BITS
+                                + DENY
+                                + END_BIT);
+
+        BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput);
+
+        // Right after construction, the read bits count should be 0.
+        assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(0);
+
+        // Get next 10 bits should result with 10 bits read.
+        bitTrackedInputStream.getNext(10);
+        assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(10);
+
+        // When we move the cursor 8 bytes, we should point to 64 bits.
+        bitTrackedInputStream.setCursorToByteLocation(8);
+        assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(64);
+
+        // Read until the end and the total size of the input stream should be available.
+        while (bitTrackedInputStream.hasNext()) {
+            bitTrackedInputStream.getNext(1);
+        }
+        assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(128);
+    }
+
+    @Test
+    public void testBitInputStreamOperationsStillWork() throws IOException {
+        String packageName = "com.test.app";
+        byte[] testInput =
+                getBytes(
+                        IS_NOT_HASHED
+                                + getBits(packageName.length(), VALUE_SIZE_BITS)
+                                + getValueBits(packageName));
+
+        BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput);
+        assertThat(bitTrackedInputStream.getReadBitsCount()).isEqualTo(0);
+
+        // Read until the string parameter.
+        String stringValue = BinaryFileOperations.getStringValue(bitTrackedInputStream);
+
+        // Verify that the read bytes are counted.
+        assertThat(stringValue).isEqualTo(packageName);
+        assertThat(bitTrackedInputStream.getReadBitsCount()).isGreaterThan(0);
+    }
+
+    @Test
+    public void testBitTrackedInputStream_moveCursorForwardFailsIfAlreadyRead() throws IOException {
+        String packageName = "com.test.app";
+        byte[] testInput =
+                getBytes(
+                        IS_NOT_HASHED
+                                + getBits(packageName.length(), VALUE_SIZE_BITS)
+                                + getValueBits(packageName));
+
+        BitTrackedInputStream bitTrackedInputStream = new BitTrackedInputStream(testInput);
+
+        // Read more than two bytes.
+        bitTrackedInputStream.getNext(20);
+
+        // Ask to move the cursor to the second byte.
+        assertThrows(
+                IllegalStateException.class,
+                () -> bitTrackedInputStream.setCursorToByteLocation(2));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
similarity index 95%
rename from services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java
rename to services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
index 5ecb8b5c..c7cc343 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
@@ -14,12 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.server.integrity.serializer;
+package com.android.server.integrity.model;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.android.server.integrity.model.BitOutputStream;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
index 9cc0ed8..881b3d6 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
@@ -97,8 +97,10 @@
     private static final byte[] DEFAULT_FORMAT_VERSION_BYTES =
             getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS));
 
+    private static final List<RuleIndexRange> NO_INDEXING = Collections.emptyList();
+
     @Test
-    public void testBinaryStream_validCompoundFormula() throws Exception {
+    public void testBinaryStream_validCompoundFormula_noIndexing() throws Exception {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -131,13 +133,13 @@
                                                 /* isHashedValue= */ false))),
                         Rule.DENY);
 
-        List<Rule> rules = binaryParser.parse(inputStream);
+        List<Rule> rules = binaryParser.parse(inputStream, NO_INDEXING);
 
         assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
     }
 
     @Test
-    public void testBinaryString_validCompoundFormula_notConnector() throws Exception {
+    public void testBinaryString_validCompoundFormula_notConnector_noIndexing() throws Exception {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -175,7 +177,7 @@
     }
 
     @Test
-    public void testBinaryString_validCompoundFormula_andConnector() throws Exception {
+    public void testBinaryString_validCompoundFormula_andConnector_noIndexing() throws Exception {
         String packageName = "com.test.app";
         String appCertificate = "test_cert";
         String ruleBits =
@@ -223,7 +225,7 @@
     }
 
     @Test
-    public void testBinaryString_validCompoundFormula_orConnector() throws Exception {
+    public void testBinaryString_validCompoundFormula_orConnector_noIndexing() throws Exception {
         String packageName = "com.test.app";
         String appCertificate = "test_cert";
         String ruleBits =
@@ -272,7 +274,7 @@
     }
 
     @Test
-    public void testBinaryString_validAtomicFormula_stringValue() throws Exception {
+    public void testBinaryString_validAtomicFormula_stringValue_noIndexing() throws Exception {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -304,7 +306,7 @@
     }
 
     @Test
-    public void testBinaryString_validAtomicFormula_hashedValue() throws Exception {
+    public void testBinaryString_validAtomicFormula_hashedValue_noIndexing() throws Exception {
         String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
         String ruleBits =
                 START_BIT
@@ -337,7 +339,7 @@
     }
 
     @Test
-    public void testBinaryString_validAtomicFormula_integerValue() throws Exception {
+    public void testBinaryString_validAtomicFormula_integerValue_noIndexing() throws Exception {
         int versionCode = 1;
         String ruleBits =
                 START_BIT
@@ -365,7 +367,7 @@
     }
 
     @Test
-    public void testBinaryString_validAtomicFormula_booleanValue() throws Exception {
+    public void testBinaryString_validAtomicFormula_booleanValue_noIndexing() throws Exception {
         String isPreInstalled = "1";
         String ruleBits =
                 START_BIT
@@ -392,7 +394,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidAtomicFormula() throws Exception {
+    public void testBinaryString_invalidAtomicFormula_noIndexing() {
         int versionCode = 1;
         String ruleBits =
                 START_BIT
@@ -415,7 +417,7 @@
     }
 
     @Test
-    public void testBinaryString_withNoRuleList() throws RuleParseException {
+    public void testBinaryString_withNoRuleList_noIndexing() throws RuleParseException {
         ByteBuffer rule = ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length);
         rule.put(DEFAULT_FORMAT_VERSION_BYTES);
         RuleParser binaryParser = new RuleBinaryParser();
@@ -426,7 +428,7 @@
     }
 
     @Test
-    public void testBinaryString_withEmptyRule() throws RuleParseException {
+    public void testBinaryString_withEmptyRule_noIndexing() {
         String ruleBits = START_BIT;
         byte[] ruleBytes = getBytes(ruleBits);
         ByteBuffer rule =
@@ -442,7 +444,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidCompoundFormula_invalidNumberOfFormulas() throws Exception {
+    public void testBinaryString_invalidCompoundFormula_invalidNumberOfFormulas_noIndexing() {
         String packageName = "com.test.app";
         String appCertificate = "test_cert";
         String ruleBits =
@@ -478,7 +480,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidOperator() throws Exception {
+    public void testBinaryString_invalidRule_invalidOperator_noIndexing() {
         int versionCode = 1;
         String ruleBits =
                 START_BIT
@@ -506,7 +508,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidEffect() throws Exception {
+    public void testBinaryString_invalidRule_invalidEffect_noIndexing() {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -536,7 +538,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidConnector() throws Exception {
+    public void testBinaryString_invalidRule_invalidConnector_noIndexing() {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -566,7 +568,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidKey() throws Exception {
+    public void testBinaryString_invalidRule_invalidKey_noIndexing() {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -596,7 +598,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidSeparator() throws Exception {
+    public void testBinaryString_invalidRule_invalidSeparator_noIndexing() {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
@@ -626,7 +628,7 @@
     }
 
     @Test
-    public void testBinaryString_invalidRule_invalidEndMarker() throws Exception {
+    public void testBinaryString_invalidRule_invalidEndMarker_noIndexing() {
         String packageName = "com.test.app";
         String ruleBits =
                 START_BIT
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
new file mode 100644
index 0000000..742952e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.parser;
+
+import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
+import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
+import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
+import static com.android.server.integrity.utils.TestUtils.getBits;
+import static com.android.server.integrity.utils.TestUtils.getBytes;
+import static com.android.server.integrity.utils.TestUtils.getValueBits;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.integrity.AppInstallMetadata;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class RuleIndexingControllerTest {
+
+    @Test
+    public void verifyIndexRangeSearchIsCorrect() throws IOException {
+        InputStream inputStream = obtainDefaultIndexingMapForTest();
+
+        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
+
+        AppInstallMetadata appInstallMetadata =
+                new AppInstallMetadata.Builder()
+                        .setPackageName("ddd")
+                        .setAppCertificate("777")
+                        .build();
+
+        List<RuleIndexRange> resultingIndexes =
+                indexingController.identifyRulesToEvaluate(appInstallMetadata);
+
+        assertThat(resultingIndexes)
+                .containsExactly(
+                        new RuleIndexRange(200, 300),
+                        new RuleIndexRange(700, 800),
+                        new RuleIndexRange(900, 945));
+    }
+
+    @Test
+    public void verifyIndexRangeSearchIsCorrect_keysInFirstAndLastBlock() throws IOException {
+        InputStream inputStream = obtainDefaultIndexingMapForTest();
+
+        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
+
+        AppInstallMetadata appInstallMetadata =
+                new AppInstallMetadata.Builder()
+                        .setPackageName("bbb")
+                        .setAppCertificate("999")
+                        .build();
+
+        List<RuleIndexRange> resultingIndexes =
+                indexingController.identifyRulesToEvaluate(appInstallMetadata);
+
+        assertThat(resultingIndexes)
+                .containsExactly(
+                        new RuleIndexRange(100, 200),
+                        new RuleIndexRange(800, 900),
+                        new RuleIndexRange(900, 945));
+    }
+
+    @Test
+    public void verifyIndexRangeSearchIsCorrect_keysMatchWithValues() throws IOException {
+        InputStream inputStream = obtainDefaultIndexingMapForTest();
+
+        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
+
+        AppInstallMetadata appInstallMetadata =
+                new AppInstallMetadata.Builder()
+                        .setPackageName("ccc")
+                        .setAppCertificate("444")
+                        .build();
+
+        List<RuleIndexRange> resultingIndexes =
+                indexingController.identifyRulesToEvaluate(appInstallMetadata);
+
+        assertThat(resultingIndexes)
+                .containsExactly(
+                        new RuleIndexRange(200, 300),
+                        new RuleIndexRange(700, 800),
+                        new RuleIndexRange(900, 945));
+    }
+
+    @Test
+    public void verifyIndexRangeSearchIsCorrect_noIndexesAvailable() throws IOException {
+        byte[] stringBytes =
+                getBytes(
+                        getKeyValueString(START_INDEXING_KEY, 100)
+                                + getKeyValueString(END_INDEXING_KEY, 500)
+                                + getKeyValueString(START_INDEXING_KEY, 500)
+                                + getKeyValueString(END_INDEXING_KEY, 900)
+                                + getKeyValueString(START_INDEXING_KEY, 900)
+                                + getKeyValueString(END_INDEXING_KEY, 945));
+        ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
+        rule.put(stringBytes);
+        InputStream inputStream = new ByteArrayInputStream(rule.array());
+
+        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
+
+        AppInstallMetadata appInstallMetadata =
+                new AppInstallMetadata.Builder()
+                        .setPackageName("ccc")
+                        .setAppCertificate("444")
+                        .build();
+
+        List<RuleIndexRange> resultingIndexes =
+                indexingController.identifyRulesToEvaluate(appInstallMetadata);
+
+        assertThat(resultingIndexes)
+                .containsExactly(
+                        new RuleIndexRange(100, 500),
+                        new RuleIndexRange(500, 900),
+                        new RuleIndexRange(900, 945));
+    }
+
+    private static InputStream obtainDefaultIndexingMapForTest() {
+        byte[] stringBytes =
+                getBytes(
+                        getKeyValueString(START_INDEXING_KEY, 100)
+                                + getKeyValueString("ccc", 200)
+                                + getKeyValueString("eee", 300)
+                                + getKeyValueString("hhh", 400)
+                                + getKeyValueString(END_INDEXING_KEY, 500)
+                                + getKeyValueString(START_INDEXING_KEY, 500)
+                                + getKeyValueString("111", 600)
+                                + getKeyValueString("444", 700)
+                                + getKeyValueString("888", 800)
+                                + getKeyValueString(END_INDEXING_KEY, 900)
+                                + getKeyValueString(START_INDEXING_KEY, 900)
+                                + getKeyValueString(END_INDEXING_KEY, 945));
+        ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
+        rule.put(stringBytes);
+        return new ByteArrayInputStream(rule.array());
+    }
+
+    private static String getKeyValueString(String key, int value) {
+        String isNotHashed = "0";
+        return isNotHashed
+                + getBits(key.length(), VALUE_SIZE_BITS)
+                + getValueBits(key)
+                + getBits(value, /* numOfBits= */ 32);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java
index a14197b..6944aee 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java
@@ -73,7 +73,7 @@
                                                 /* isHashedValue= */ false))),
                         Rule.DENY);
 
-        List<Rule> rules = xmlParser.parse(inputStream);
+        List<Rule> rules = xmlParser.parse(inputStream, Collections.emptyList());
 
         assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
     }
@@ -623,7 +623,7 @@
         assertExpectException(
                 RuleParseException.class,
                 /* expectedExceptionMessageRegex */ "Rules must start with RuleList <RL> tag",
-                () -> xmlParser.parse(inputStream));
+                () -> xmlParser.parse(inputStream, Collections.emptyList()));
     }
 
     private String generateTagWithAttribute(
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index 821d97a..670bd81 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -67,13 +67,25 @@
 public class PlatformKeyManagerTest {
 
     private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
-    private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
+    private static final int MIN_GENERATION_ID = 1000000;
+    private static final int PRIMARY_USER_ID_FIXTURE = 0;
     private static final int USER_ID_FIXTURE = 42;
     private static final long USER_SID = 4200L;
     private static final String KEY_ALGORITHM = "AES";
     private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
     private static final String TESTING_KEYSTORE_KEY_ALIAS = "testing-key-store-key-alias";
 
+    private static final String ENCRYPTION_KEY_ALIAS_1 =
+             "com.android.server.locksettings.recoverablekeystore/platform/42/1000000/encrypt";
+    private static final String DECRYPTION_KEY_ALIAS_1 =
+             "com.android.server.locksettings.recoverablekeystore/platform/42/1000000/decrypt";
+    private static final String DECRYPTION_KEY_FOR_ALIAS_PRIMARY_USER_1 =
+             "com.android.server.locksettings.recoverablekeystore/platform/0/1000000/decrypt";
+    private static final String ENCRYPTION_KEY_ALIAS_2 =
+             "com.android.server.locksettings.recoverablekeystore/platform/42/1000001/encrypt";
+    private static final String DECRYPTION_KEY_ALIAS_2 =
+             "com.android.server.locksettings.recoverablekeystore/platform/42/1000001/decrypt";
+
     @Mock private Context mContext;
     @Mock private KeyStoreProxy mKeyStoreProxy;
     @Mock private KeyguardManager mKeyguardManager;
@@ -114,7 +126,7 @@
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_1),
                 any(),
                 any());
     }
@@ -156,7 +168,7 @@
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any(),
                 any());
     }
@@ -187,19 +199,33 @@
     }
 
     @Test
-    public void init_createsDecryptKeyWithAuthenticationRequired() throws Exception {
-        mPlatformKeyManager.init(USER_ID_FIXTURE);
+    public void init_primaryUser_createsDecryptKeyWithUnlockedDeviceRequired() throws Exception {
+        mPlatformKeyManager.init(PRIMARY_USER_ID_FIXTURE);
 
-        assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired());
+        assertTrue(getDecryptKeyProtectionForPrimaryUser().isUnlockedDeviceRequired());
     }
 
     @Test
-    public void init_createsDecryptKeyWithAuthenticationValidFor15Seconds() throws Exception {
+    public void init_primaryUser_createsDecryptKeyWithoutAuthenticationRequired() throws Exception {
+        mPlatformKeyManager.init(PRIMARY_USER_ID_FIXTURE);
+
+        assertFalse(getDecryptKeyProtectionForPrimaryUser().isUserAuthenticationRequired());
+    }
+
+    @Test
+    public void init_secondaryUser_createsDecryptKeyWithoutUnlockedDeviceRequired()
+            throws Exception {
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        assertEquals(
-                USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS,
-                getDecryptKeyProtection().getUserAuthenticationValidityDurationSeconds());
+        assertFalse(getDecryptKeyProtection().isUnlockedDeviceRequired());
+    }
+
+    @Test
+    public void init_secondaryUserUser_createsDecryptKeyWithAuthenticationRequired()
+            throws Exception {
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
+
+        assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired());
     }
 
     @Test
@@ -219,7 +245,7 @@
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy, never()).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any(),
                 any());
     }
@@ -231,7 +257,7 @@
         expectThrows(RemoteException.class, () -> mPlatformKeyManager.init(USER_ID_FIXTURE));
 
         verify(mKeyStoreProxy, never()).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any(),
                 any());
     }
@@ -251,15 +277,15 @@
     public void init_savesGenerationIdToDatabase() throws Exception {
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        assertEquals(1,
+        assertEquals(MIN_GENERATION_ID,
                 mRecoverableKeyStoreDb.getPlatformKeyGenerationId(USER_ID_FIXTURE));
     }
 
     @Test
-    public void init_setsGenerationIdTo1() throws Exception {
+    public void init_setsGenerationId() throws Exception {
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+        assertEquals(MIN_GENERATION_ID, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
     }
 
     @Test
@@ -268,22 +294,20 @@
 
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+        assertEquals(MIN_GENERATION_ID + 1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
     }
 
     @Test
     public void init_doesNotIncrementGenerationIdIfKeyAvailable() throws Exception {
         mPlatformKeyManager.init(USER_ID_FIXTURE);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
 
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+        assertEquals(MIN_GENERATION_ID, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
     }
 
     @Test
@@ -294,226 +318,194 @@
     @Test
     public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy.getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any())).thenReturn(generateAndroidKeyStoreKey());
 
         mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any());
     }
 
     @Test
     public void getDecryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(false); // was removed
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
         when(mKeyStoreProxy.getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any())).thenReturn(generateAndroidKeyStoreKey());
 
         mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).containsAlias(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"));
+                eq(DECRYPTION_KEY_ALIAS_1));
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getDecryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(false); // was removed
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).containsAlias(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+                eq(ENCRYPTION_KEY_ALIAS_1));
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getEncryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
         doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_1),
                 any());
 
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_1),
                 any());
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getDecryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
         doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any());
         when(mKeyStoreProxy.getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any())).thenReturn(generateAndroidKeyStoreKey());
 
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).containsAlias(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"));
+                eq(DECRYPTION_KEY_ALIAS_1));
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getEncryptKey_generatesNewKeyIfDecryptKeyIsUnrecoverable() throws Exception {
         doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any());
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(false); // was removed
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getEncryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(false); // was removed
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).containsAlias(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+                eq(ENCRYPTION_KEY_ALIAS_1));
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getEncryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(false); // was removed
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).containsAlias(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+                eq(ENCRYPTION_KEY_ALIAS_1));
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getEncryptKey_getsEncryptKeyWithCorrectAlias() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_1),
                 any());
     }
 
@@ -523,7 +515,7 @@
 
         mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
-        assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+        assertEquals(MIN_GENERATION_ID + 1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
     }
 
     @Test
@@ -533,17 +525,17 @@
         mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).deleteEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+                eq(ENCRYPTION_KEY_ALIAS_1));
         verify(mKeyStoreProxy).deleteEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"));
+                eq(DECRYPTION_KEY_ALIAS_1));
 
         mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
         // Removes second generation keys.
         verify(mKeyStoreProxy).deleteEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"));
+                eq(ENCRYPTION_KEY_ALIAS_2));
         verify(mKeyStoreProxy).deleteEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"));
+                eq(DECRYPTION_KEY_ALIAS_2));
     }
 
     @Test
@@ -553,7 +545,7 @@
         mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_2),
                 any(),
                 any());
     }
@@ -565,14 +557,14 @@
         mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any(),
                 any());
     }
 
     private KeyProtection getEncryptKeyProtection() throws Exception {
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_1),
                 any(),
                 mProtectionParameterCaptor.capture());
         return (KeyProtection) mProtectionParameterCaptor.getValue();
@@ -580,7 +572,15 @@
 
     private KeyProtection getDecryptKeyProtection() throws Exception {
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
+                any(),
+                mProtectionParameterCaptor.capture());
+        return (KeyProtection) mProtectionParameterCaptor.getValue();
+    }
+
+    private KeyProtection getDecryptKeyProtectionForPrimaryUser() throws Exception {
+        verify(mKeyStoreProxy).setEntry(
+                eq(DECRYPTION_KEY_FOR_ALIAS_PRIMARY_USER_1),
                 any(),
                 mProtectionParameterCaptor.capture());
         return (KeyProtection) mProtectionParameterCaptor.getValue();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 6890017..ac74470 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -29,6 +29,7 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeast;
@@ -84,7 +85,7 @@
 import java.util.ArrayList;
 import java.util.Map;
 import java.util.Random;
-import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
 
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
@@ -157,7 +158,7 @@
     @Mock private PlatformKeyManager mPlatformKeyManager;
     @Mock private ApplicationKeyStorage mApplicationKeyStorage;
     @Mock private CleanupManager mCleanupManager;
-    @Mock private ExecutorService mExecutorService;
+    @Mock private ScheduledExecutorService mExecutorService;
     @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
 
     private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
@@ -1253,7 +1254,7 @@
         mRecoverableKeyStoreManager.lockScreenSecretAvailable(
                 LockPatternUtils.CREDENTIAL_TYPE_PATTERN, "password".getBytes(), 11);
 
-        verify(mExecutorService).execute(any());
+        verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any());
     }
 
     @Test
@@ -1263,7 +1264,7 @@
                 "password".getBytes(),
                 11);
 
-        verify(mExecutorService).execute(any());
+        verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any());
     }
 
     private static byte[] encryptedApplicationKey(
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 28e6830..e1c489e 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -37,6 +37,7 @@
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.TAG_ALL;
+import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkTemplate.buildTemplateMobileAll;
 import static android.net.NetworkTemplate.buildTemplateWifi;
 import static android.net.TrafficStats.MB_IN_BYTES;
@@ -1273,11 +1274,11 @@
             history.recordData(start, end,
                     new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0));
             stats.clear();
-            stats.addValues(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
+            stats.addEntry(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
                     DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
-            stats.addValues(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
+            stats.addEntry(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
                     DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
-            stats.addValues(IFACE_ALL, UID_C, SET_ALL, TAG_ALL,
+            stats.addEntry(IFACE_ALL, UID_C, SET_ALL, TAG_ALL,
                     DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
 
             reset(mNotifManager);
@@ -1301,9 +1302,9 @@
             history.recordData(start, end,
                     new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0));
             stats.clear();
-            stats.addValues(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
+            stats.addEntry(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
                     DataUnit.MEGABYTES.toBytes(960), 0, 0, 0, 0);
-            stats.addValues(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
+            stats.addEntry(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
                     DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
 
             reset(mNotifManager);
@@ -1758,6 +1759,57 @@
     }
 
     /**
+     * Test that when StatsProvider triggers limit reached, new limit will be calculated and
+     * re-armed.
+     */
+    @Test
+    public void testStatsProviderLimitReached() throws Exception {
+        final int CYCLE_DAY = 15;
+
+        final NetworkStats stats = new NetworkStats(0L, 1);
+        stats.addEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
+                2999, 1, 2000, 1, 0);
+        when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
+                .thenReturn(stats.getTotalBytes());
+        when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
+                .thenReturn(stats);
+
+        // Get active mobile network in place
+        expectMobileDefaults();
+        mService.updateNetworks();
+        verify(mStatsService).setStatsProviderLimit(TEST_IFACE, Long.MAX_VALUE);
+
+        // Set limit to 10KB.
+        setNetworkPolicies(new NetworkPolicy(
+                sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, WARNING_DISABLED, 10000L,
+                true));
+        postMsgAndWaitForCompletion();
+
+        // Verifies that remaining quota is set to providers.
+        verify(mStatsService).setStatsProviderLimit(TEST_IFACE, 10000L - 4999L);
+
+        reset(mStatsService);
+
+        // Increase the usage.
+        stats.addEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
+                1000, 1, 999, 1, 0);
+        when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
+                .thenReturn(stats.getTotalBytes());
+        when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
+                .thenReturn(stats);
+
+        // Simulates that limit reached fires earlier by provider, but actually the quota is not
+        // yet reached.
+        final NetworkPolicyManagerInternal npmi = LocalServices
+                .getService(NetworkPolicyManagerInternal.class);
+        npmi.onStatsProviderLimitReached("TEST");
+
+        // Verifies that the limit reached leads to a force update.
+        postMsgAndWaitForCompletion();
+        verify(mStatsService).forceUpdate();
+    }
+
+    /**
      * Exhaustively test isUidNetworkingBlocked to output the expected results based on external
      * conditions.
      */
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index c478ec4..15327b6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -156,8 +156,7 @@
         if (isMultiPackage) {
             params.isMultiPackage = true;
         }
-        InstallSource installSource = InstallSource.create("testInstaller", null, "testInstaller",
-                false);
+        InstallSource installSource = InstallSource.create("testInstaller", null, "testInstaller");
         return new PackageInstallerSession(
                 /* callback */ null,
                 /* context */null,
diff --git a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
new file mode 100644
index 0000000..7666ab9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.SensorManager;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ServiceManager;
+import android.os.Vibrator;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.os.BatteryStatsImpl;
+import com.android.server.LocalServices;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.power.batterysaver.BatterySaverPolicy;
+import com.android.server.power.batterysaver.BatterySavingStats;
+import com.android.server.statusbar.StatusBarManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link com.android.server.power.Notifier}
+ */
+public class NotifierTest {
+    private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
+    private static final int USER_ID = 0;
+
+    @Mock private BatterySaverPolicy mBatterySaverPolicyMock;
+    @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
+    @Mock private Notifier mNotifierMock;
+    @Mock private WirelessChargerDetector mWirelessChargerDetectorMock;
+    @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock;
+    @Mock private SystemPropertiesWrapper mSystemPropertiesMock;
+    @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
+    @Mock private BatteryStatsImpl mBatteryStats;
+    @Mock private Vibrator mVibrator;
+    @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
+
+    private PowerManagerService mService;
+    private Context mContextSpy;
+    private Resources mResourcesSpy;
+    private TestLooper mTestLooper = new TestLooper();
+    private Notifier mNotifier;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+        LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal);
+
+        mContextSpy = spy(new TestableContext(InstrumentationRegistry.getContext()));
+        mResourcesSpy = spy(mContextSpy.getResources());
+        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
+        when(mContextSpy.getSystemService(Vibrator.class)).thenReturn(mVibrator);
+
+        mService = new PowerManagerService(mContextSpy, mInjector);
+    }
+
+    @Test
+    public void testVibrateEnabled_wiredCharging() {
+        createNotifier();
+
+        // GIVEN the charging vibration is enabled
+        enableChargingVibration(true);
+
+        // WHEN wired charging starts
+        mNotifier.onWiredChargingStarted(USER_ID);
+        mTestLooper.dispatchAll();
+
+        // THEN the device vibrates once
+        verify(mVibrator, times(1)).vibrate(any(), any());
+    }
+
+    @Test
+    public void testVibrateDisabled_wiredCharging() {
+        createNotifier();
+
+        // GIVEN the charging vibration is disabled
+        enableChargingVibration(false);
+
+        // WHEN wired charging starts
+        mNotifier.onWiredChargingStarted(USER_ID);
+        mTestLooper.dispatchAll();
+
+        // THEN the device doesn't vibrate
+        verify(mVibrator, never()).vibrate(any(), any());
+    }
+
+    @Test
+    public void testVibrateEnabled_wirelessCharging() {
+        createNotifier();
+
+        // GIVEN the charging vibration is enabled
+        enableChargingVibration(true);
+
+        // WHEN wireless charging starts
+        mNotifier.onWirelessChargingStarted(5, USER_ID);
+        mTestLooper.dispatchAll();
+
+        // THEN the device vibrates once
+        verify(mVibrator, times(1)).vibrate(any(), any());
+    }
+
+    @Test
+    public void testVibrateDisabled_wirelessCharging() {
+        createNotifier();
+
+        // GIVEN the charging vibration is disabeld
+        enableChargingVibration(false);
+
+        // WHEN wireless charging starts
+        mNotifier.onWirelessChargingStarted(5, USER_ID);
+        mTestLooper.dispatchAll();
+
+        // THEN the device doesn't vibrate
+        verify(mVibrator, never()).vibrate(any(), any());
+    }
+
+    @Test
+    public void testVibrateEnabled_dndOn() {
+        createNotifier();
+
+        // GIVEN the charging vibration is enabled but dnd is on
+        enableChargingVibration(true);
+        enableChargingFeedback(
+                /* chargingFeedbackEnabled */ true,
+                /* dndOn */ true);
+
+        // WHEN wired charging starts
+        mNotifier.onWiredChargingStarted(USER_ID);
+        mTestLooper.dispatchAll();
+
+        // THEN the device doesn't vibrate
+        verify(mVibrator, never()).vibrate(any(), any());
+    }
+
+    @Test
+    public void testWirelessAnimationEnabled() {
+        // GIVEN the wireless charging animation is enabled
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim))
+                .thenReturn(true);
+        createNotifier();
+
+        // WHEN wireless charging starts
+        mNotifier.onWirelessChargingStarted(5, USER_ID);
+        mTestLooper.dispatchAll();
+
+        // THEN the charging animation is triggered
+        verify(mStatusBarManagerInternal, times(1)).showChargingAnimation(5);
+    }
+
+    @Test
+    public void testWirelessAnimationDisabled() {
+        // GIVEN the wireless charging animation is disabled
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim))
+                .thenReturn(false);
+        createNotifier();
+
+        // WHEN wireless charging starts
+        mNotifier.onWirelessChargingStarted(5, USER_ID);
+        mTestLooper.dispatchAll();
+
+        // THEN the charging animation never gets called
+        verify(mStatusBarManagerInternal, never()).showChargingAnimation(anyInt());
+    }
+
+    private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() {
+        @Override
+        Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
+                SuspendBlocker suspendBlocker, WindowManagerPolicy policy) {
+            return mNotifierMock;
+        }
+
+        @Override
+        SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) {
+            return super.createSuspendBlocker(service, name);
+        }
+
+        @Override
+        BatterySaverPolicy createBatterySaverPolicy(
+                Object lock, Context context, BatterySavingStats batterySavingStats) {
+            return mBatterySaverPolicyMock;
+        }
+
+        @Override
+        PowerManagerService.NativeWrapper createNativeWrapper() {
+            return mNativeWrapperMock;
+        }
+
+        @Override
+        WirelessChargerDetector createWirelessChargerDetector(
+                SensorManager sensorManager, SuspendBlocker suspendBlocker, Handler handler) {
+            return mWirelessChargerDetectorMock;
+        }
+
+        @Override
+        AmbientDisplayConfiguration createAmbientDisplayConfiguration(Context context) {
+            return mAmbientDisplayConfigurationMock;
+        }
+
+        @Override
+        InattentiveSleepWarningController createInattentiveSleepWarningController() {
+            return mInattentiveSleepWarningControllerMock;
+        }
+
+        @Override
+        public SystemPropertiesWrapper createSystemPropertiesWrapper() {
+            return mSystemPropertiesMock;
+        }
+    };
+
+    private void enableChargingFeedback(boolean chargingFeedbackEnabled, boolean dndOn) {
+        // enable/disable charging feedback
+        Settings.Secure.putIntForUser(
+                mContextSpy.getContentResolver(),
+                Settings.Secure.CHARGING_SOUNDS_ENABLED,
+                chargingFeedbackEnabled ? 1 : 0,
+                USER_ID);
+
+        // toggle on/off dnd
+        Settings.Global.putInt(
+                mContextSpy.getContentResolver(),
+                Settings.Global.ZEN_MODE,
+                dndOn ? Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+                        : Settings.Global.ZEN_MODE_OFF);
+    }
+
+    private void enableChargingVibration(boolean enable) {
+        enableChargingFeedback(true, false);
+
+        Settings.Secure.putIntForUser(
+                mContextSpy.getContentResolver(),
+                Settings.Secure.CHARGING_VIBRATION_ENABLED,
+                enable ? 1 : 0,
+                USER_ID);
+    }
+
+    private void createNotifier() {
+        mNotifier = new Notifier(
+                mTestLooper.getLooper(),
+                mContextSpy,
+                IBatteryStats.Stub.asInterface(ServiceManager.getService(
+                        BatteryStats.SERVICE_NAME)),
+                mInjector.createSuspendBlocker(mService, "testBlocker"),
+                null);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
index a83d940..f871203 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
@@ -98,7 +98,7 @@
             final int[] installedUsers) {
         return new PackageRollbackInfo(
                 new VersionedPackage(packageName, 2), new VersionedPackage(packageName, 1),
-                new IntArray(), new ArrayList<>(), false, IntArray.wrap(installedUsers),
+                new IntArray(), new ArrayList<>(), false, false, IntArray.wrap(installedUsers),
                 new SparseLongArray());
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
index 757a884..d0d2edc 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
@@ -87,13 +87,15 @@
             + "[{'versionRolledBackFrom':{'packageName':'blah','longVersionCode':55},"
             + "'versionRolledBackTo':{'packageName':'blah1','longVersionCode':50},'pendingBackups':"
             + "[59,1245,124544],'pendingRestores':[{'userId':498,'appId':32322,'seInfo':'wombles'},"
-            + "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'installedUsers':"
+            + "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'isApkInApex':false,"
+            + "'installedUsers':"
             + "[498468432,1111,98464],'ceSnapshotInodes':[{'userId':1,'ceSnapshotInode':-6},"
             + "{'userId':2222,'ceSnapshotInode':81641654445},{'userId':546546,"
             + "'ceSnapshotInode':345689375}]},{'versionRolledBackFrom':{'packageName':'chips',"
             + "'longVersionCode':28},'versionRolledBackTo':{'packageName':'com.chips.test',"
             + "'longVersionCode':48},'pendingBackups':[5],'pendingRestores':[{'userId':18,"
-            + "'appId':-12,'seInfo':''}],'isApex':false,'installedUsers':[55,79],"
+            + "'appId':-12,'seInfo':''}],'isApex':false,'isApkInApex':false,"
+            + "'installedUsers':[55,79],"
             + "'ceSnapshotInodes':[]}],'isStaged':false,'causePackages':[{'packageName':'hello',"
             + "'longVersionCode':23},{'packageName':'something','longVersionCode':999}],"
             + "'committedSessionId':45654465},'timestamp':'2019-10-01T12:29:08.855Z',"
@@ -155,7 +157,7 @@
         PackageRollbackInfo pkgInfo1 =
                 new PackageRollbackInfo(new VersionedPackage("com.made.up", 18),
                         new VersionedPackage("com.something.else", 5), new IntArray(),
-                        new ArrayList<>(), false, new IntArray(), new SparseLongArray());
+                        new ArrayList<>(), false, false, new IntArray(), new SparseLongArray());
         pkgInfo1.getPendingBackups().add(8);
         pkgInfo1.getPendingBackups().add(888);
         pkgInfo1.getPendingBackups().add(88885);
@@ -175,7 +177,7 @@
         PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo(
                 new VersionedPackage("another.package", 2),
                 new VersionedPackage("com.test.ing", 48888), new IntArray(), new ArrayList<>(),
-                false, new IntArray(), new SparseLongArray());
+                false, false, new IntArray(), new SparseLongArray());
         pkgInfo2.getPendingBackups().add(57);
 
         pkgInfo2.getPendingRestores().add(
@@ -205,7 +207,7 @@
 
         PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("blah", 55),
                 new VersionedPackage("blah1", 50), new IntArray(), new ArrayList<>(),
-                false, new IntArray(), new SparseLongArray());
+                false, false, new IntArray(), new SparseLongArray());
         pkgInfo1.getPendingBackups().add(59);
         pkgInfo1.getPendingBackups().add(1245);
         pkgInfo1.getPendingBackups().add(124544);
@@ -224,7 +226,7 @@
 
         PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo(new VersionedPackage("chips", 28),
                 new VersionedPackage("com.chips.test", 48), new IntArray(), new ArrayList<>(),
-                false, new IntArray(), new SparseLongArray());
+                false, false, new IntArray(), new SparseLongArray());
         pkgInfo2.getPendingBackups().add(5);
 
         pkgInfo2.getPendingRestores().add(
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
index e368d63..164c883 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
@@ -295,7 +295,8 @@
             String packageName, long fromVersion, long toVersion, boolean isApex) {
         return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion),
                 new VersionedPackage(packageName, toVersion),
-                new IntArray(), new ArrayList<>(), isApex, new IntArray(), new SparseLongArray());
+                new IntArray(), new ArrayList<>(), isApex, false, new IntArray(),
+                new SparseLongArray());
     }
 
     private static class PackageRollbackInfoForPackage implements
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index f8915c0..cc170af 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -37,6 +37,7 @@
 import android.hardware.soundtrigger.V2_3.OptionalModelParameterRange;
 import android.media.audio.common.AudioChannelMask;
 import android.media.audio.common.AudioFormat;
+import android.media.soundtrigger_middleware.AudioCapabilities;
 import android.media.soundtrigger_middleware.ConfidenceLevel;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
@@ -59,6 +60,7 @@
 import android.os.IHwBinder;
 import android.os.IHwInterface;
 import android.os.RemoteException;
+import android.util.Pair;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -155,7 +157,19 @@
         return properties;
     }
 
-    private static void validateDefaultProperties(SoundTriggerModuleProperties properties,
+    private static android.hardware.soundtrigger.V2_3.Properties createDefaultProperties_2_3(
+            boolean supportConcurrentCapture) {
+        android.hardware.soundtrigger.V2_3.Properties properties =
+                new android.hardware.soundtrigger.V2_3.Properties();
+        properties.base = createDefaultProperties(supportConcurrentCapture);
+        properties.supportedModelArch = "supportedModelArch";
+        properties.audioCapabilities =
+                android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION
+                        | android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION;
+        return properties;
+    }
+
+    private void validateDefaultProperties(SoundTriggerModuleProperties properties,
             boolean supportConcurrentCapture) {
         assertEquals("implementor", properties.implementor);
         assertEquals("description", properties.description);
@@ -173,8 +187,23 @@
         assertEquals(supportConcurrentCapture, properties.concurrentCapture);
         assertTrue(properties.triggerInEvent);
         assertEquals(432, properties.powerConsumptionMw);
+
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            assertEquals("supportedModelArch", properties.supportedModelArch);
+            assertEquals(AudioCapabilities.ECHO_CANCELLATION | AudioCapabilities.NOISE_SUPPRESSION,
+                    properties.audioCapabilities);
+        } else {
+            assertEquals("", properties.supportedModelArch);
+            assertEquals(0, properties.audioCapabilities);
+        }
     }
 
+    private void verifyNotGetProperties() throws RemoteException {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver,
+                    never()).getProperties(any());
+        }
+    }
 
     private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_0(
             int hwHandle,
@@ -290,15 +319,36 @@
                     properties);
             return null;
         }).when(mHalDriver).getProperties(any());
+
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+                    (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+            doAnswer(invocation -> {
+                android.hardware.soundtrigger.V2_3.Properties properties =
+                        createDefaultProperties_2_3(
+                                supportConcurrentCapture);
+                ((android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getProperties_2_3Callback)
+                        invocation.getArgument(
+                        0)).onValues(0,
+                        properties);
+                return null;
+            }).when(driver).getProperties_2_3(any());
+        }
+
         mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider);
     }
 
-    private int loadGenericModel_2_0(ISoundTriggerModule module, int hwHandle)
-            throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_0(ISoundTriggerModule module,
+            int hwHandle) throws RemoteException {
         SoundModel model = createGenericSoundModel();
         ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel> modelCaptor =
                 ArgumentCaptor.forClass(
                         android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
                     invocation.getArgument(1);
@@ -317,7 +367,8 @@
             modelEvent.model = hwHandle;
             callback.soundModelCallback(modelEvent, callbackCookie);
             return null;
-        }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+        }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), callbackCaptor.capture(),
+                cookieCaptor.capture(), any());
 
         when(mAudioSessionProvider.acquireSession()).thenReturn(
                 new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -334,17 +385,23 @@
         assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.vendorUuid));
         assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.data.toArray());
 
-        return handle;
+        return new Pair<>(handle,
+                new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
     }
 
-    private int loadGenericModel_2_1(ISoundTriggerModule module, int hwHandle)
-            throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_1(ISoundTriggerModule module,
+            int hwHandle) throws RemoteException {
         android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
                 (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
         SoundModel model = createGenericSoundModel();
         ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel> modelCaptor =
                 ArgumentCaptor.forClass(
                         android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
                     invocation.getArgument(1);
@@ -363,7 +420,8 @@
             modelEvent.header.model = hwHandle;
             callback.soundModelCallback_2_1(modelEvent, callbackCookie);
             return null;
-        }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+        }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(),
+                cookieCaptor.capture(), any());
 
         when(mAudioSessionProvider.acquireSession()).thenReturn(
                 new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -381,10 +439,12 @@
         assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
                 HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.data));
 
-        return handle;
+        return new Pair<>(handle,
+                new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
     }
 
-    private int loadGenericModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadGenericModel(ISoundTriggerModule module,
+            int hwHandle) throws RemoteException {
         if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
             return loadGenericModel_2_1(module, hwHandle);
         } else {
@@ -392,12 +452,17 @@
         }
     }
 
-    private int loadPhraseModel_2_0(ISoundTriggerModule module, int hwHandle)
-            throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_0(ISoundTriggerModule module,
+            int hwHandle) throws RemoteException {
         PhraseSoundModel model = createPhraseSoundModel();
         ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel>
                 modelCaptor = ArgumentCaptor.forClass(
                 android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
                     invocation.getArgument(
@@ -419,7 +484,8 @@
             modelEvent.model = hwHandle;
             callback.soundModelCallback(modelEvent, callbackCookie);
             return null;
-        }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+        }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), callbackCaptor.capture(),
+                cookieCaptor.capture(), any());
 
         when(mAudioSessionProvider.acquireSession()).thenReturn(
                 new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -451,11 +517,12 @@
                         | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
                 hidlPhrase.recognitionModes);
 
-        return handle;
+        return new Pair<>(handle,
+                new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
     }
 
-    private int loadPhraseModel_2_1(ISoundTriggerModule module, int hwHandle)
-            throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_1(ISoundTriggerModule module,
+            int hwHandle) throws RemoteException {
         android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
                 (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
 
@@ -463,6 +530,11 @@
         ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel>
                 modelCaptor = ArgumentCaptor.forClass(
                 android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
                     invocation.getArgument(
@@ -484,7 +556,8 @@
             modelEvent.header.model = hwHandle;
             callback.soundModelCallback_2_1(modelEvent, callbackCookie);
             return null;
-        }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+        }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(),
+                cookieCaptor.capture(), any());
 
         when(mAudioSessionProvider.acquireSession()).thenReturn(
                 new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -517,10 +590,12 @@
                         | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
                 hidlPhrase.recognitionModes);
 
-        return handle;
+        return new Pair<>(handle,
+                new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
     }
 
-    private int loadPhraseModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel(
+            ISoundTriggerModule module, int hwHandle) throws RemoteException {
         if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
             return loadPhraseModel_2_1(module, hwHandle);
         } else {
@@ -535,18 +610,14 @@
         verify(mAudioSessionProvider).releaseSession(101);
     }
 
-    private SoundTriggerHwCallback startRecognition_2_0(ISoundTriggerModule module, int handle,
+    private void startRecognition_2_0(ISoundTriggerModule module, int handle,
             int hwHandle) throws RemoteException {
         ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig>
                 configCaptor = ArgumentCaptor.forClass(
                 android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig.class);
-        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
-                ArgumentCaptor.forClass(
-                        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
-        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
 
-        when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(),
-                callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+        when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(), any(), anyInt()))
+                .thenReturn(0);
 
         RecognitionConfig config = createRecognitionConfig();
 
@@ -569,11 +640,9 @@
         assertEquals(234, halLevel.userId);
         assertEquals(34, halLevel.levelPercent);
         assertArrayEquals(new Byte[]{5, 4, 3, 2, 1}, halConfig.data.toArray());
-
-        return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
     }
 
-    private SoundTriggerHwCallback startRecognition_2_1(ISoundTriggerModule module, int handle,
+    private void startRecognition_2_1(ISoundTriggerModule module, int handle,
             int hwHandle) throws RemoteException {
         android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
                 (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
@@ -581,13 +650,9 @@
         ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig>
                 configCaptor = ArgumentCaptor.forClass(
                 android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig.class);
-        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
-                ArgumentCaptor.forClass(
-                        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
-        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
 
-        when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(),
-                callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+        when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(), any(), anyInt()))
+                .thenReturn(0);
 
         RecognitionConfig config = createRecognitionConfig();
 
@@ -611,16 +676,56 @@
         assertEquals(34, halLevel.levelPercent);
         assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
                 HidlMemoryUtil.hidlMemoryToByteArray(halConfig.data));
-
-        return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
     }
 
-    private SoundTriggerHwCallback startRecognition(ISoundTriggerModule module, int handle,
+    private void startRecognition_2_3(ISoundTriggerModule module, int handle,
             int hwHandle) throws RemoteException {
-        if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
-            return startRecognition_2_1(module, handle, hwHandle);
+        android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+                (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+        ArgumentCaptor<android.hardware.soundtrigger.V2_3.RecognitionConfig>
+                configCaptor = ArgumentCaptor.forClass(
+                android.hardware.soundtrigger.V2_3.RecognitionConfig.class);
+
+        when(driver.startRecognition_2_3(eq(hwHandle), configCaptor.capture())).thenReturn(0);
+
+        RecognitionConfig config = createRecognitionConfig();
+
+        module.startRecognition(handle, config);
+        verify(driver).startRecognition_2_3(eq(hwHandle), any());
+
+        android.hardware.soundtrigger.V2_3.RecognitionConfig halConfigExtended =
+                configCaptor.getValue();
+        android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig halConfig_2_1 =
+                halConfigExtended.base;
+
+        assertTrue(halConfig_2_1.header.captureRequested);
+        assertEquals(102, halConfig_2_1.header.captureHandle);
+        assertEquals(103, halConfig_2_1.header.captureDevice);
+        assertEquals(1, halConfig_2_1.header.phrases.size());
+        android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+                halConfig_2_1.header.phrases.get(0);
+        assertEquals(123, halPhraseExtra.id);
+        assertEquals(4, halPhraseExtra.confidenceLevel);
+        assertEquals(5, halPhraseExtra.recognitionModes);
+        assertEquals(1, halPhraseExtra.levels.size());
+        android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0);
+        assertEquals(234, halLevel.userId);
+        assertEquals(34, halLevel.levelPercent);
+        assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
+                HidlMemoryUtil.hidlMemoryToByteArray(halConfig_2_1.data));
+        assertEquals(AudioCapabilities.ECHO_CANCELLATION
+                | AudioCapabilities.NOISE_SUPPRESSION, halConfigExtended.audioCapabilities);
+    }
+
+    private void startRecognition(ISoundTriggerModule module, int handle,
+            int hwHandle) throws RemoteException {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            startRecognition_2_3(module, handle, hwHandle);
+        } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+            startRecognition_2_1(module, handle, hwHandle);
         } else {
-            return startRecognition_2_0(module, handle, hwHandle);
+            startRecognition_2_0(module, handle, hwHandle);
         }
     }
 
@@ -635,6 +740,8 @@
         config.phraseRecognitionExtras[0].levels[0].userId = 234;
         config.phraseRecognitionExtras[0].levels[0].levelPercent = 34;
         config.data = new byte[]{5, 4, 3, 2, 1};
+        config.audioCapabilities = AudioCapabilities.ECHO_CANCELLATION
+                | AudioCapabilities.NOISE_SUPPRESSION;
         return config;
     }
 
@@ -650,6 +757,9 @@
         if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
             verify((android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver,
                     never()).startRecognition_2_1(anyInt(), any(), any(), anyInt());
+        } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver,
+                    never()).startRecognition_2_3(anyInt(), any());
         }
     }
 
@@ -716,6 +826,7 @@
         SoundTriggerModuleProperties properties = allDescriptors[0].properties;
 
         validateDefaultProperties(properties, true);
+        verifyNotGetProperties();
     }
 
     @Test
@@ -760,7 +871,7 @@
         ISoundTriggerModule module = mService.attach(0, callback);
 
         final int hwHandle = 7;
-        int handle = loadGenericModel(module, hwHandle);
+        int handle = loadGenericModel(module, hwHandle).first;
         unloadModel(module, handle, hwHandle);
         module.detach();
     }
@@ -772,7 +883,7 @@
         ISoundTriggerModule module = mService.attach(0, callback);
 
         final int hwHandle = 73;
-        int handle = loadPhraseModel(module, hwHandle);
+        int handle = loadPhraseModel(module, hwHandle).first;
         unloadModel(module, handle, hwHandle);
         module.detach();
     }
@@ -785,7 +896,7 @@
 
         // Load the model.
         final int hwHandle = 7;
-        int handle = loadGenericModel(module, hwHandle);
+        int handle = loadGenericModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -806,7 +917,7 @@
 
         // Load the model.
         final int hwHandle = 67;
-        int handle = loadPhraseModel(module, hwHandle);
+        int handle = loadPhraseModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -827,10 +938,12 @@
 
         // Load the model.
         final int hwHandle = 7;
-        int handle = loadGenericModel(module, hwHandle);
+        Pair<Integer, SoundTriggerHwCallback> modelHandles = loadGenericModel(module, hwHandle);
+        int handle = modelHandles.first;
+        SoundTriggerHwCallback hwCallback = modelHandles.second;
 
         // Initiate a recognition.
-        SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+        startRecognition(module, handle, hwHandle);
 
         // Signal a capture from the driver.
         hwCallback.sendRecognitionEvent(hwHandle,
@@ -856,10 +969,12 @@
 
         // Load the model.
         final int hwHandle = 7;
-        int handle = loadPhraseModel(module, hwHandle);
+        Pair<Integer, SoundTriggerHwCallback> modelHandles = loadPhraseModel(module, hwHandle);
+        int handle = modelHandles.first;
+        SoundTriggerHwCallback hwCallback = modelHandles.second;
 
         // Initiate a recognition.
-        SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+        startRecognition(module, handle, hwHandle);
 
         // Signal a capture from the driver.
         hwCallback.sendPhraseRecognitionEvent(hwHandle,
@@ -892,10 +1007,12 @@
 
         // Load the model.
         final int hwHandle = 17;
-        int handle = loadGenericModel(module, hwHandle);
+        Pair<Integer, SoundTriggerHwCallback> modelHandles = loadGenericModel(module, hwHandle);
+        int handle = modelHandles.first;
+        SoundTriggerHwCallback hwCallback = modelHandles.second;
 
         // Initiate a recognition.
-        SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+        startRecognition(module, handle, hwHandle);
 
         // Force a trigger.
         module.forceRecognitionEvent(handle);
@@ -935,10 +1052,12 @@
 
         // Load the model.
         final int hwHandle = 17;
-        int handle = loadPhraseModel(module, hwHandle);
+        Pair<Integer, SoundTriggerHwCallback> modelHandles = loadPhraseModel(module, hwHandle);
+        int handle = modelHandles.first;
+        SoundTriggerHwCallback hwCallback = modelHandles.second;
 
         // Initiate a recognition.
-        SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+        startRecognition(module, handle, hwHandle);
 
         // Force a trigger.
         module.forceRecognitionEvent(handle);
@@ -975,7 +1094,7 @@
 
         // Load the model.
         final int hwHandle = 11;
-        int handle = loadGenericModel(module, hwHandle);
+        int handle = loadGenericModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -1023,7 +1142,7 @@
 
         // Load the model.
         final int hwHandle = 11;
-        int handle = loadPhraseModel(module, hwHandle);
+        int handle = loadPhraseModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -1071,7 +1190,7 @@
 
         // Load the model.
         final int hwHandle = 13;
-        int handle = loadGenericModel(module, hwHandle);
+        int handle = loadGenericModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -1107,7 +1226,7 @@
 
         // Load the model.
         final int hwHandle = 13;
-        int handle = loadPhraseModel(module, hwHandle);
+        int handle = loadPhraseModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -1144,7 +1263,7 @@
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 12;
-        int modelHandle = loadGenericModel(module, hwHandle);
+        int modelHandle = loadGenericModel(module, hwHandle).first;
 
         doAnswer((Answer<Void>) invocation -> {
             android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
@@ -1180,7 +1299,7 @@
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 13;
-        int modelHandle = loadGenericModel(module, hwHandle);
+        int modelHandle = loadGenericModel(module, hwHandle).first;
 
         ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
                 ModelParameter.THRESHOLD_FACTOR);
@@ -1201,7 +1320,7 @@
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 13;
-        int modelHandle = loadGenericModel(module, hwHandle);
+        int modelHandle = loadGenericModel(module, hwHandle).first;
 
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
@@ -1234,7 +1353,7 @@
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 14;
-        int modelHandle = loadGenericModel(module, hwHandle);
+        int modelHandle = loadGenericModel(module, hwHandle).first;
 
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getParameterCallback
@@ -1266,7 +1385,7 @@
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 17;
-        int modelHandle = loadGenericModel(module, hwHandle);
+        int modelHandle = loadGenericModel(module, hwHandle).first;
 
         when(driver.setParameter(hwHandle,
                 android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR,
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 71b568c..ae53692 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -37,7 +37,7 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import androidx.test.runner.AndroidJUnit4;
 
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index ca6fd08..8a3183f 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -26,11 +25,10 @@
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
 import android.icu.util.Calendar;
 import android.icu.util.GregorianCalendar;
 import android.icu.util.TimeZone;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -78,8 +76,7 @@
 
         long expectedSystemClockMillis =
                 mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
-        mScript.verifySystemClockWasSetAndResetCallTracking(
-                expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+        mScript.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
                 .assertLatestPhoneSuggestion(phoneId, timeSuggestion);
     }
 
@@ -118,8 +115,7 @@
                     mScript.calculateTimeInMillisForNow(timeSuggestion1.getUtcTime());
 
             mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
-                    .verifySystemClockWasSetAndResetCallTracking(
-                            expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+                    .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
                     .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
         }
 
@@ -146,8 +142,7 @@
                     mScript.calculateTimeInMillisForNow(timeSuggestion3.getUtcTime());
 
             mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
-                    .verifySystemClockWasSetAndResetCallTracking(
-                            expectedSystemClockMillis3, true /* expectNetworkBroadcast */)
+                    .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis3)
                     .assertLatestPhoneSuggestion(phoneId, timeSuggestion3);
         }
     }
@@ -175,8 +170,7 @@
                     mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime());
 
             mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
-                    .verifySystemClockWasSetAndResetCallTracking(
-                            expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+                    .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
                     .assertLatestPhoneSuggestion(phone1Id, null)
                     .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
         }
@@ -193,8 +187,7 @@
                     mScript.calculateTimeInMillisForNow(phone1TimeSuggestion.getUtcTime());
 
             mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion)
-                    .verifySystemClockWasSetAndResetCallTracking(
-                            expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+                    .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
                     .assertLatestPhoneSuggestion(phone1Id, phone1TimeSuggestion);
 
         }
@@ -227,8 +220,7 @@
                     mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime());
 
             mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
-                    .verifySystemClockWasSetAndResetCallTracking(
-                            expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+                    .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
                     .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
         }
     }
@@ -265,8 +257,7 @@
         mScript.simulateTimePassing();
         long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(utcTime1);
         mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
                 .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
 
         // The UTC time increment should be larger than the system clock update threshold so we
@@ -304,8 +295,7 @@
         PhoneTimeSuggestion timeSuggestion4 =
                 createPhoneTimeSuggestion(phoneId, utcTime4);
         mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis4, true /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4)
                 .assertLatestPhoneSuggestion(phoneId, timeSuggestion4);
     }
 
@@ -339,8 +329,7 @@
 
         // Turn on auto time detection.
         mScript.simulateAutoTimeDetectionToggle()
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
                 .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
 
         // Turn off auto time detection.
@@ -367,8 +356,7 @@
 
         // Turn on auto time detection.
         mScript.simulateAutoTimeDetectionToggle()
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis2, true /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2)
                 .assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
     }
 
@@ -388,7 +376,7 @@
                 mScript.calculateTimeInMillisForNow(phoneSuggestion.getUtcTime());
         mScript.simulatePhoneTimeSuggestion(phoneSuggestion)
                 .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis, true /* expectedNetworkBroadcast */)
+                        expectedSystemClockMillis  /* expectedNetworkBroadcast */)
                 .assertLatestPhoneSuggestion(phoneId, phoneSuggestion);
 
         // Look inside and check what the strategy considers the current best phone suggestion.
@@ -416,8 +404,7 @@
         long expectedSystemClockMillis =
                 mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
         mScript.simulateManualTimeSuggestion(timeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis, false /* expectNetworkBroadcast */);
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
     }
 
     @Test
@@ -439,8 +426,7 @@
         long expectedAutoClockMillis =
                 mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime());
         mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedAutoClockMillis, true /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
                 .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
 
         // Simulate the passage of time.
@@ -463,8 +449,7 @@
         long expectedManualClockMillis =
                 mScript.calculateTimeInMillisForNow(manualTimeSuggestion.getUtcTime());
         mScript.simulateManualTimeSuggestion(manualTimeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedManualClockMillis, false /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedManualClockMillis)
                 .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
 
         // Simulate the passage of time.
@@ -475,8 +460,7 @@
 
         expectedAutoClockMillis =
                 mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime());
-        mScript.verifySystemClockWasSetAndResetCallTracking(
-                        expectedAutoClockMillis, true /* expectNetworkBroadcast */)
+        mScript.verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
                 .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
 
         // Switch back to manual - nothing should happen to the clock.
@@ -514,8 +498,7 @@
         long expectedSystemClockMillis =
                 mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
         mScript.simulateNetworkTimeSuggestion(timeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis, false /* expectNetworkBroadcast */);
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
     }
 
     @Test
@@ -550,8 +533,7 @@
         mScript.simulateTimePassing(smallTimeIncrementMillis)
                 .simulateNetworkTimeSuggestion(networkTimeSuggestion1)
                 .verifySystemClockWasSetAndResetCallTracking(
-                        mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime()),
-                        false /* expectNetworkBroadcast */);
+                        mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime()));
 
         // Check internal state.
         mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, null)
@@ -570,8 +552,7 @@
         mScript.simulateTimePassing(smallTimeIncrementMillis)
                 .simulatePhoneTimeSuggestion(phoneTimeSuggestion)
                 .verifySystemClockWasSetAndResetCallTracking(
-                        mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()),
-                        true /* expectNetworkBroadcast */);
+                        mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()));
 
         // Check internal state.
         mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
@@ -622,8 +603,7 @@
 
         // Verify the latest network time now wins.
         mScript.verifySystemClockWasSetAndResetCallTracking(
-                mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime()),
-                false /* expectNetworkTimeBroadcast */);
+                mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime()));
 
         // Check internal state.
         mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
@@ -645,7 +625,6 @@
 
         // Tracking operations.
         private boolean mSystemClockWasSet;
-        private Intent mBroadcastSent;
 
         @Override
         public int systemClockUpdateThresholdMillis() {
@@ -689,12 +668,6 @@
             mWakeLockAcquired = false;
         }
 
-        @Override
-        public void sendStickyBroadcast(Intent intent) {
-            assertNotNull(intent);
-            mBroadcastSent = intent;
-        }
-
         // Methods below are for managing the fake's behavior.
 
         void pokeSystemClockUpdateThreshold(int thresholdMillis) {
@@ -739,17 +712,8 @@
             assertEquals(expectedSystemClockMillis, mSystemClockMillis);
         }
 
-        void verifyIntentWasBroadcast() {
-            assertTrue(mBroadcastSent != null);
-        }
-
-        void verifyIntentWasNotBroadcast() {
-            assertNull(mBroadcastSent);
-        }
-
         void resetCallTracking() {
             mSystemClockWasSet = false;
-            mBroadcastSent = null;
         }
 
         private void assertWakeLockAcquired() {
@@ -832,17 +796,12 @@
 
         Script verifySystemClockWasNotSetAndResetCallTracking() {
             mFakeCallback.verifySystemClockNotSet();
-            mFakeCallback.verifyIntentWasNotBroadcast();
             mFakeCallback.resetCallTracking();
             return this;
         }
 
-        Script verifySystemClockWasSetAndResetCallTracking(
-                long expectedSystemClockMillis, boolean expectNetworkBroadcast) {
+        Script verifySystemClockWasSetAndResetCallTracking(long expectedSystemClockMillis) {
             mFakeCallback.verifySystemClockWasSet(expectedSystemClockMillis);
-            if (expectNetworkBroadcast) {
-                mFakeCallback.verifyIntentWasBroadcast();
-            }
             mFakeCallback.resetCallTracking();
             return this;
         }
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java
index 239d413..f1e9191 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java
@@ -18,7 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import androidx.test.runner.AndroidJUnit4;
 
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 12ba219..1dd7e64 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -365,8 +365,9 @@
     public void testSetAppStandbyBucket() throws Exception {
         // For a known package, standby bucket should be set properly
         reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
+        mInjector.mElapsedRealtime = HOUR_MS;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_TIMEOUT, HOUR_MS);
+                REASON_MAIN_TIMEOUT);
         assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
 
         // For an unknown package, standby bucket should not be set, hence NEVER is returned
@@ -374,7 +375,7 @@
         mController.clearAppIdleForPackage(PACKAGE_UNKNOWN, USER_ID);
         isPackageInstalled = false; // Mock package is not installed
         mController.setAppStandbyBucket(PACKAGE_UNKNOWN, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_TIMEOUT, HOUR_MS);
+                REASON_MAIN_TIMEOUT);
         isPackageInstalled = true; // Reset mocked variable for other tests
         assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_UNKNOWN));
     }
@@ -468,12 +469,13 @@
     @Test
     public void testPredictionTimedout() throws Exception {
         // Set it to timeout or usage, so that prediction can override it
+        mInjector.mElapsedRealtime = HOUR_MS;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
-                REASON_MAIN_TIMEOUT, HOUR_MS);
+                REASON_MAIN_TIMEOUT);
         assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
 
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_PREDICTED, HOUR_MS);
+                REASON_MAIN_PREDICTED);
         assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
 
         // Fast forward 12 hours
@@ -497,29 +499,31 @@
     @Test
     public void testOverrides() throws Exception {
         // Can force to NEVER
+        mInjector.mElapsedRealtime = HOUR_MS;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
-                REASON_MAIN_FORCED, 1 * HOUR_MS);
+                REASON_MAIN_FORCED);
         assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1));
 
         // Prediction can't override FORCED reason
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_FORCED, 1 * HOUR_MS);
+                REASON_MAIN_FORCED);
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
-                REASON_MAIN_PREDICTED, 1 * HOUR_MS);
+                REASON_MAIN_PREDICTED);
         assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
 
         // Prediction can't override NEVER
+        mInjector.mElapsedRealtime = 2 * HOUR_MS;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
-                REASON_MAIN_DEFAULT, 2 * HOUR_MS);
+                REASON_MAIN_DEFAULT);
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_PREDICTED, 2 * HOUR_MS);
+                REASON_MAIN_PREDICTED);
         assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1));
 
         // Prediction can't set to NEVER
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_USAGE, 2 * HOUR_MS);
+                REASON_MAIN_USAGE);
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
-                REASON_MAIN_PREDICTED, 2 * HOUR_MS);
+                REASON_MAIN_PREDICTED);
         assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
     }
 
@@ -530,7 +534,7 @@
 
         mInjector.mElapsedRealtime = 2000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
         // bucketing works after timeout
@@ -554,15 +558,17 @@
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
-                REASON_MAIN_PREDICTED, 1000);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
+        mInjector.mElapsedRealtime = 2000 + mController.mStrongUsageTimeoutMillis;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, 2000 + mController.mStrongUsageTimeoutMillis);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_WORKING_SET);
 
+        mInjector.mElapsedRealtime = 2000 + mController.mNotificationSeenTimeoutMillis;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, 2000 + mController.mNotificationSeenTimeoutMillis);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_FREQUENT);
     }
 
@@ -582,18 +588,18 @@
         // Still in ACTIVE after first USER_INTERACTION times out
         mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis + 1000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
         // Both timed out, so NOTIFICATION_SEEN timeout should be effective
         mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis * 2 + 2000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_WORKING_SET);
 
         mInjector.mElapsedRealtime = mController.mNotificationSeenTimeoutMillis + 2000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_RARE);
     }
 
@@ -625,7 +631,7 @@
         mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100;
         // Make sure app is in NEVER bucket
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
-                REASON_MAIN_FORCED, mInjector.mElapsedRealtime);
+                REASON_MAIN_FORCED);
         mController.checkIdleStates(USER_ID);
         assertBucket(STANDBY_BUCKET_NEVER);
 
@@ -670,7 +676,7 @@
         // Predict to ACTIVE
         mInjector.mElapsedRealtime += 1000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
         // CheckIdleStates should not change the prediction
@@ -687,7 +693,7 @@
         // Predict to FREQUENT
         mInjector.mElapsedRealtime = RARE_THRESHOLD;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED);
         assertBucket(STANDBY_BUCKET_FREQUENT);
 
         // Add a short timeout event
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index eb45960..bd7d9ec 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -20,7 +20,6 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 
 import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 
 import static org.mockito.Matchers.any;
@@ -65,11 +64,10 @@
 
         NotificationChannel updatedChannel =
                 new NotificationChannel("a", "", IMPORTANCE_HIGH);
-        when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(false)))
+        when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(null), eq(false)))
                 .thenReturn(updatedChannel);
 
         assertNull(extractor.process(r));
         assertEquals(updatedChannel, r.getChannel());
     }
-
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 172df99..62f5230 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -112,6 +112,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -133,6 +134,7 @@
 import android.testing.TestablePermissions;
 import android.testing.TestableResources;
 import android.text.Html;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -475,7 +477,7 @@
         when(mPreferencesHelper.bubblesEnabled()).thenReturn(globalEnabled);
         when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(pkgEnabled);
         when(mPreferencesHelper.getNotificationChannel(
-                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
+                anyString(), anyInt(), anyString(), eq(null), anyBoolean())).thenReturn(
                 mTestNotificationChannel);
         when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
                 mTestNotificationChannel.getImportance());
@@ -1762,7 +1764,7 @@
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0,
                 generateNotificationRecord(null, tv).getNotification(), 0);
         verify(mPreferencesHelper, times(1)).getNotificationChannel(
-                anyString(), anyInt(), eq("foo"), anyBoolean());
+                anyString(), anyInt(), eq("foo"), eq(null), anyBoolean());
     }
 
     @Test
@@ -1777,7 +1779,8 @@
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv",
                 0, generateNotificationRecord(null, tv).getNotification(), 0);
         verify(mPreferencesHelper, times(1)).getNotificationChannel(
-                anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
+                anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null),
+                anyBoolean());
     }
 
     @Test
@@ -3210,9 +3213,11 @@
         final NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 1, 2, true);
         mService.mNotificationDelegate.onNotificationVisibilityChanged(
                 new NotificationVisibility[] {nv}, new NotificationVisibility[]{});
+        verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(true));
         assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen());
         mService.mNotificationDelegate.onNotificationVisibilityChanged(
                 new NotificationVisibility[] {}, new NotificationVisibility[]{nv});
+        verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(false));
         assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen());
     }
 
@@ -4463,6 +4468,16 @@
     }
 
     @Test
+    public void testOnPanelRevealedAndHidden() {
+        int items = 5;
+        mService.mNotificationDelegate.onPanelRevealed(false, items);
+        verify(mAssistants, times(1)).onPanelRevealed(eq(items));
+
+        mService.mNotificationDelegate.onPanelHidden();
+        verify(mAssistants, times(1)).onPanelHidden();
+    }
+
+    @Test
     public void testOnNotificationSmartReplySent() {
         final int replyIndex = 2;
         final String reply = "Hello";
@@ -5044,10 +5059,9 @@
                 nb.build(), new UserHandle(mUid), null, 0);
         // Make sure it has foreground service
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
 
         // yes phone call, yes person, yes foreground service, but not allowed, no bubble
@@ -5802,4 +5816,79 @@
 
         verify(mHistoryManager, times(1)).addNotification(any());
     }
+
+    @Test
+    public void createConversationNotificationChannel() throws Exception {
+        NotificationChannel original = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+        original.setAllowBubbles(!original.canBubble());
+        original.setShowBadge(!original.canShowBadge());
+
+        Parcel parcel = Parcel.obtain();
+        original.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NotificationChannel orig = NotificationChannel.CREATOR.createFromParcel(parcel);
+        assertEquals(original, orig);
+        assertFalse(TextUtils.isEmpty(orig.getName()));
+
+        mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(
+                orig)));
+
+        mBinderService.createConversationNotificationChannelForPackage(PKG, mUid, orig, "friend");
+
+        NotificationChannel friendChannel = mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, original.getId(), "friend");
+
+        assertEquals(original.getName(), friendChannel.getName());
+        assertEquals(original.getId(), friendChannel.getParentChannelId());
+        assertEquals("friend", friendChannel.getConversationId());
+        assertEquals(null, original.getConversationId());
+        assertEquals(original.canShowBadge(), friendChannel.canShowBadge());
+        assertEquals(original.canBubble(), friendChannel.canBubble());
+        assertFalse(original.getId().equals(friendChannel.getId()));
+        assertNotNull(friendChannel.getId());
+    }
+
+    @Test
+    public void deleteConversationNotificationChannels() throws Exception {
+        NotificationChannel messagesParent =
+                new NotificationChannel("messages", "messages", IMPORTANCE_HIGH);
+        Parcel msgParcel = Parcel.obtain();
+        messagesParent.writeToParcel(msgParcel, 0);
+        msgParcel.setDataPosition(0);
+
+        NotificationChannel callsParent =
+                new NotificationChannel("calls", "calls", IMPORTANCE_HIGH);
+        Parcel callParcel = Parcel.obtain();
+        callsParent.writeToParcel(callParcel, 0);
+        callParcel.setDataPosition(0);
+
+        mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(
+                messagesParent, callsParent)));
+
+        String conversationId = "friend";
+
+        mBinderService.createConversationNotificationChannelForPackage(
+                PKG, mUid, NotificationChannel.CREATOR.createFromParcel(msgParcel), conversationId);
+        mBinderService.createConversationNotificationChannelForPackage(
+                PKG, mUid, NotificationChannel.CREATOR.createFromParcel(callParcel),
+                conversationId);
+
+        NotificationChannel messagesChild = mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, messagesParent.getId(), conversationId);
+        NotificationChannel callsChild = mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, callsParent.getId(), conversationId);
+
+        assertEquals(messagesParent.getId(), messagesChild.getParentChannelId());
+        assertEquals(conversationId, messagesChild.getConversationId());
+
+        assertEquals(callsParent.getId(), callsChild.getParentChannelId());
+        assertEquals(conversationId, callsChild.getConversationId());
+
+        mBinderService.deleteConversationNotificationChannels(PKG, mUid, conversationId);
+
+        assertNull(mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, messagesParent.getId(), conversationId));
+        assertNull(mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, callsParent.getId(), conversationId));
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 8961796..7173e0c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.notification;
 
+import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -224,6 +225,26 @@
         assertEquals(expected.getGroup(), actual.getGroup());
         assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
         assertEquals(expected.getLightColor(), actual.getLightColor());
+        assertEquals(expected.getParentChannelId(), actual.getParentChannelId());
+        assertEquals(expected.getConversationId(), actual.getConversationId());
+    }
+
+    private void compareChannelsParentChild(NotificationChannel parent,
+            NotificationChannel actual, String conversationId) {
+        assertEquals(parent.getName(), actual.getName());
+        assertEquals(parent.getDescription(), actual.getDescription());
+        assertEquals(parent.shouldVibrate(), actual.shouldVibrate());
+        assertEquals(parent.shouldShowLights(), actual.shouldShowLights());
+        assertEquals(parent.getImportance(), actual.getImportance());
+        assertEquals(parent.getLockscreenVisibility(), actual.getLockscreenVisibility());
+        assertEquals(parent.getSound(), actual.getSound());
+        assertEquals(parent.canBypassDnd(), actual.canBypassDnd());
+        assertTrue(Arrays.equals(parent.getVibrationPattern(), actual.getVibrationPattern()));
+        assertEquals(parent.getGroup(), actual.getGroup());
+        assertEquals(parent.getAudioAttributes(), actual.getAudioAttributes());
+        assertEquals(parent.getLightColor(), actual.getLightColor());
+        assertEquals(parent.getId(), actual.getParentChannelId());
+        assertEquals(conversationId, actual.getConversationId());
     }
 
     private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
@@ -2786,4 +2807,40 @@
         assertEquals(user10Importance, mHelper.getNotificationChannel(
                 pkg, uidList10[0], channelId, false).getImportance());
     }
+
+    @Test
+    public void testGetConversationNotificationChannel() {
+        String conversationId = "friend";
+
+        NotificationChannel parent =
+                new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
+
+        NotificationChannel friend = new NotificationChannel(String.format(
+                CONVERSATION_CHANNEL_ID_FORMAT, parent.getId(), conversationId),
+                "messages", IMPORTANCE_DEFAULT);
+        friend.setConversationId(parent.getId(), conversationId);
+        mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
+
+        compareChannelsParentChild(parent, mHelper.getNotificationChannel(
+                PKG_O, UID_O, parent.getId(), conversationId, false), conversationId);
+    }
+
+    @Test
+    public void testConversationNotificationChannelsRequireParents() {
+        String parentId = "does not exist";
+        String conversationId = "friend";
+
+        NotificationChannel friend = new NotificationChannel(String.format(
+                CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId),
+                "messages", IMPORTANCE_DEFAULT);
+        friend.setConversationId(parentId, conversationId);
+
+        try {
+            mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
+            fail("allowed creation of conversation channel without a parent");
+        } catch (IllegalArgumentException e) {
+            // good
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 73b2f6b..a3e94599 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -521,11 +521,12 @@
     }
 
     @Test
-    public void testShouldPauseWhenMakeClientVisible() {
+    public void testShouldStartWhenMakeClientActive() {
         ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
         topActivity.setOccludesParent(false);
         mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing");
-        mActivity.makeClientVisible();
+        mActivity.setVisibility(true);
+        mActivity.makeActiveIfNeeded(null /* activeActivity */);
         assertEquals(STARTED, mActivity.getState());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
index 574517a..71390db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
@@ -40,7 +40,7 @@
  * Tests for the {@link ActivityStack} class.
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:AnimatingActivityRegistryTest
+ *  atest WmTests:AnimatingActivityRegistryTest
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 1311889..f6213bd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -42,7 +42,7 @@
 
 /**
  * Build/Install/Run:
- *  atest FrameworksServicesTests:AppTransitionControllerTest
+ *  atest WmTests:AppTransitionControllerTest
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
index 0ad0f95..1dda535 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -60,7 +60,7 @@
  * appropriately.
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:BoundsAnimationControllerTests
+ *  atest WmTests:BoundsAnimationControllerTests
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 0aa6961..e8f7849 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -40,7 +40,7 @@
 
 /**
  * Build/Install/Run:
- *  atest FrameworksServicesTests:DimmerTests
+ *  atest WmTests:DimmerTests
  */
 @Presubmit
 @RunWith(WindowTestRunner.class)
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 73dd2df..f2ba97c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -57,6 +57,8 @@
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -86,6 +88,7 @@
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.ViewRootImpl;
+import android.view.WindowManager;
 import android.view.test.InsetsModeSession;
 
 import androidx.test.filters.SmallTest;
@@ -561,8 +564,7 @@
         final DisplayContent dc = createNewDisplay();
         final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w");
 
-        dc.setLayoutNeeded();
-        dc.performLayout(true /* initial */, false /* updateImeWindows */);
+        performLayout(dc);
 
         assertThat(win.mLayoutSeq, is(dc.mLayoutSeq));
     }
@@ -829,8 +831,7 @@
         win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
         win.setSystemGestureExclusion(Collections.singletonList(new Rect(10, 20, 30, 40)));
 
-        dc.setLayoutNeeded();
-        dc.performLayout(true /* initial */, false /* updateImeWindows */);
+        performLayout(dc);
 
         win.setHasSurface(true);
         dc.updateSystemGestureExclusion();
@@ -866,8 +867,7 @@
         win2.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
         win2.setSystemGestureExclusion(Collections.singletonList(new Rect(20, 30, 40, 50)));
 
-        dc.setLayoutNeeded();
-        dc.performLayout(true /* initial */, false /* updateImeWindows */);
+        performLayout(dc);
 
         win.setHasSurface(true);
         win2.setHasSurface(true);
@@ -898,8 +898,7 @@
         win2.getAttrs().height = 10;
         win2.setSystemGestureExclusion(Collections.emptyList());
 
-        dc.setLayoutNeeded();
-        dc.performLayout(true /* initial */, false /* updateImeWindows */);
+        performLayout(dc);
 
         win.setHasSurface(true);
         win2.setHasSurface(true);
@@ -922,8 +921,7 @@
                         | SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
         win.mActivityRecord.mTargetSdk = P;
 
-        dc.setLayoutNeeded();
-        dc.performLayout(true /* initial */, false /* updateImeWindows */);
+        performLayout(dc);
 
         win.setHasSurface(true);
 
@@ -935,6 +933,22 @@
     }
 
     @Test
+    public void testRequestResizeForEmptyFrames() {
+        final WindowState win = mChildAppWindowAbove;
+        makeWindowVisible(win, win.getParentWindow());
+        win.setRequestedSize(mDisplayContent.mBaseDisplayWidth, 0 /* height */);
+        win.mAttrs.width = win.mAttrs.height = WindowManager.LayoutParams.WRAP_CONTENT;
+        win.mAttrs.gravity = Gravity.CENTER;
+        performLayout(mDisplayContent);
+
+        // The frame is empty because the requested height is zero.
+        assertTrue(win.getFrameLw().isEmpty());
+        // The window should be scheduled to resize then the client may report a new non-empty size.
+        win.updateResizingWindowIfNeeded();
+        assertThat(mWm.mResizingWindows).contains(win);
+    }
+
+    @Test
     public void testOrientationChangeLogging() {
         MetricsLogger mockLogger = mock(MetricsLogger.class);
         Configuration oldConfig = new Configuration();
@@ -1011,6 +1025,11 @@
         mWm.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateInputWindows */);
     }
 
+    private void performLayout(DisplayContent dc) {
+        dc.setLayoutNeeded();
+        dc.performLayout(true /* initial */, false /* updateImeWindows */);
+    }
+
     /**
      * Create DisplayContent that does not update display base/initial values from device to keep
      * the values set by test.
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 09ac9ce..d819b1a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -63,6 +63,26 @@
         assertEquals(Insets.of(0, 100, 0, 0),
                 mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
                         false /* ignoreVisibility */));
+        assertEquals(Insets.of(0, 100, 0, 0),
+                mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500)));
+    }
+
+    @Test
+    public void testPostLayout_givenInsets() {
+        final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime");
+        ime.getFrameLw().set(0, 0, 500, 100);
+        ime.getGivenContentInsetsLw().set(0, 0, 0, 60);
+        ime.getGivenVisibleInsetsLw().set(0, 0, 0, 75);
+        ime.mHasSurface = true;
+        mProvider.setWindow(ime, null);
+        mProvider.onPostLayout();
+        assertEquals(new Rect(0, 0, 500, 40), mProvider.getSource().getFrame());
+        assertEquals(new Rect(0, 0, 500, 25), mProvider.getSource().getVisibleFrame());
+        assertEquals(Insets.of(0, 40, 0, 0),
+                mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
+                        false /* ignoreVisibility */));
+        assertEquals(Insets.of(0, 25, 0, 0),
+                mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500)));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 112479b..1a57596 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -61,7 +61,7 @@
 
 /**
  * Build/Install/Run:
- *  atest FrameworksServicesTests:RemoteAnimationControllerTest
+ *  atest WmTests:RemoteAnimationControllerTest
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
index e011280..bac2bca 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
@@ -60,7 +60,7 @@
  * Test class for {@link SurfaceAnimationRunner}.
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:SurfaceAnimationRunnerTest
+ *  atest WmTests:SurfaceAnimationRunnerTest
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
new file mode 100644
index 0000000..8d2da1e
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.view.ITaskOrganizer;
+import android.view.SurfaceControl;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link TaskOrganizer}.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:TaskOrganizerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class TaskOrganizerTests extends WindowTestsBase {
+    private ITaskOrganizer makeAndRegisterMockOrganizer() {
+        final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
+        when(organizer.asBinder()).thenReturn(new Binder());
+
+        mWm.mAtmService.registerTaskOrganizer(organizer, WINDOWING_MODE_PINNED);
+
+        return organizer;
+    }
+
+    @Test
+    public void testAppearVanish() throws RemoteException {
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+        task.setTaskOrganizer(organizer);
+        verify(organizer).taskAppeared(any(), any());
+        assertTrue(task.isControlledByTaskOrganizer());
+
+        task.removeImmediately();
+        verify(organizer).taskVanished(any());
+    }
+
+    @Test
+    public void testSwapOrganizer() throws RemoteException {
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+        final ITaskOrganizer organizer2 = makeAndRegisterMockOrganizer();
+
+        task.setTaskOrganizer(organizer);
+        verify(organizer).taskAppeared(any(), any());
+        task.setTaskOrganizer(organizer2);
+        verify(organizer).taskVanished(any());
+        verify(organizer2).taskAppeared(any(), any());
+    }
+
+    @Test
+    public void testClearOrganizer() throws RemoteException {
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+        task.setTaskOrganizer(organizer);
+        verify(organizer).taskAppeared(any(), any());
+        assertTrue(task.isControlledByTaskOrganizer());
+
+        task.setTaskOrganizer(null);
+        verify(organizer).taskVanished(any());
+        assertFalse(task.isControlledByTaskOrganizer());
+    }
+
+    @Test
+    public void testTransferStackToOrganizer() throws RemoteException {
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final Task task2 = createTaskInStack(stack, 0 /* userId */);
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+        stack.transferToTaskOrganizer(organizer);
+
+        verify(organizer, times(2)).taskAppeared(any(), any());
+        assertTrue(task.isControlledByTaskOrganizer());
+        assertTrue(task2.isControlledByTaskOrganizer());
+
+        stack.transferToTaskOrganizer(null);
+
+        verify(organizer, times(2)).taskVanished(any());
+        assertFalse(task.isControlledByTaskOrganizer());
+        assertFalse(task2.isControlledByTaskOrganizer());
+    }
+
+    @Test
+    public void testRegisterTaskOrganizerTaskWindowingModeChanges() throws RemoteException {
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        task.setWindowingMode(WINDOWING_MODE_PINNED);
+        verify(organizer).taskAppeared(any(), any());
+        assertTrue(task.isControlledByTaskOrganizer());
+
+        task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        verify(organizer).taskVanished(any());
+        assertFalse(task.isControlledByTaskOrganizer());
+    }
+
+    @Test
+    public void testRegisterTaskOrganizerStackWindowingModeChanges() throws RemoteException {
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final Task task2 = createTaskInStack(stack, 0 /* userId */);
+        stack.setWindowingMode(WINDOWING_MODE_PINNED);
+        verify(organizer, times(2)).taskAppeared(any(), any());
+        assertTrue(task.isControlledByTaskOrganizer());
+        assertTrue(task2.isControlledByTaskOrganizer());
+
+        stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        verify(organizer, times(2)).taskVanished(any());
+        assertFalse(task.isControlledByTaskOrganizer());
+        assertFalse(task2.isControlledByTaskOrganizer());
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
index 0274b7d..e712471 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
@@ -38,7 +38,7 @@
  * Test class for {@link TaskSnapshotCache}.
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:TaskSnapshotCacheTest
+ *  atest WmTests:TaskSnapshotCacheTest
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index 8fe0cdb..2e485dd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -53,7 +53,7 @@
  * Test class for {@link TaskSnapshotController}.
  *
  * Build/Install/Run:
- *  *  atest FrameworksServicesTests:TaskSnapshotControllerTest
+ *  *  atest WmTests:TaskSnapshotControllerTest
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index b5a5790..eb8eb98 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -47,7 +47,7 @@
  * Test class for {@link TaskSnapshotPersister} and {@link TaskSnapshotLoader}
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:TaskSnapshotPersisterLoaderTest
+ *  atest WmTests:TaskSnapshotPersisterLoaderTest
  */
 @MediumTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
index 2d418ff..ed87f3a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -53,7 +53,7 @@
  * Test class for {@link TaskSnapshotSurface}.
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:TaskSnapshotSurfaceTest
+ *  atest WmTests:TaskSnapshotSurfaceTest
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
index 8936aad..3c0dd1e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -39,7 +39,7 @@
  * Tests for {@link WindowContainer#forAllWindows} and various implementations.
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:WindowContainerTraversalTests
+ *  atest WmTests:WindowContainerTraversalTests
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index e081ca3..7e248f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -92,7 +92,7 @@
  * Tests for the {@link WindowState} class.
  *
  * Build/Install/Run:
- *  atest FrameworksServicesTests:WindowStateTests
+ *  atest WmTests:WindowStateTests
  */
 @SmallTest
 @Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ea88315..31a7f24 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -44,6 +44,7 @@
 import android.view.DisplayInfo;
 import android.view.IWindow;
 import android.view.SurfaceControl.Transaction;
+import android.view.View;
 import android.view.WindowManager;
 
 import com.android.server.AttributeCache;
@@ -312,6 +313,16 @@
         }
     }
 
+    static void makeWindowVisible(WindowState... windows) {
+        for (WindowState win : windows) {
+            win.mViewVisibility = View.VISIBLE;
+            win.mRelayoutCalled = true;
+            win.mHasSurface = true;
+            win.mHidden = false;
+            win.showLw(false /* doAnimation */, false /* requestAnim */);
+        }
+    }
+
     /** Creates a {@link ActivityStack} and adds it to the specified {@link DisplayContent}. */
     ActivityStack createTaskStackOnDisplay(DisplayContent dc) {
         return createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, dc);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index c900f38..8397aa4 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1568,44 +1568,16 @@
         }
 
         @Override
-        public void setAppStandbyBucket(String packageName,
-                int bucket, int userId) {
+        public void setAppStandbyBucket(String packageName, int bucket, int userId) {
             getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE,
                     "No permission to change app standby state");
 
-            if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE
-                    || bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) {
-                throw new IllegalArgumentException("Cannot set the standby bucket to " + bucket);
-            }
             final int callingUid = Binder.getCallingUid();
-            try {
-                userId = ActivityManager.getService().handleIncomingUser(
-                        Binder.getCallingPid(), callingUid, userId, false, true,
-                        "setAppStandbyBucket", null);
-            } catch (RemoteException re) {
-                throw re.rethrowFromSystemServer();
-            }
-            final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID;
-            final boolean systemCaller = UserHandle.isCore(callingUid);
-            final int reason = systemCaller
-                    ? UsageStatsManager.REASON_MAIN_FORCED
-                    : UsageStatsManager.REASON_MAIN_PREDICTED;
+            final int callingPid = Binder.getCallingPid();
             final long token = Binder.clearCallingIdentity();
             try {
-                final int packageUid = mPackageManagerInternal.getPackageUid(packageName,
-                        PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                        | PackageManager.MATCH_DIRECT_BOOT_AWARE, userId);
-                // Caller cannot set their own standby state
-                if (packageUid == callingUid) {
-                    throw new IllegalArgumentException("Cannot set your own standby bucket");
-                }
-                if (packageUid < 0) {
-                    throw new IllegalArgumentException(
-                            "Cannot set standby bucket for non existent package (" + packageName
-                                    + ")");
-                }
-                mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason,
-                        SystemClock.elapsedRealtime(), shellCaller);
+                mAppStandby.setAppStandbyBucket(packageName, bucket, userId,
+                        callingUid, callingPid);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1643,37 +1615,11 @@
                     "No permission to change app standby state");
 
             final int callingUid = Binder.getCallingUid();
-            try {
-                userId = ActivityManager.getService().handleIncomingUser(
-                        Binder.getCallingPid(), callingUid, userId, false, true,
-                        "setAppStandbyBucket", null);
-            } catch (RemoteException re) {
-                throw re.rethrowFromSystemServer();
-            }
-            final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID;
-            final int reason = shellCaller
-                    ? UsageStatsManager.REASON_MAIN_FORCED
-                    : UsageStatsManager.REASON_MAIN_PREDICTED;
+            final int callingPid = Binder.getCallingPid();
             final long token = Binder.clearCallingIdentity();
             try {
-                final long elapsedRealtime = SystemClock.elapsedRealtime();
-                List<AppStandbyInfo> bucketList = appBuckets.getList();
-                for (AppStandbyInfo bucketInfo : bucketList) {
-                    final String packageName = bucketInfo.mPackageName;
-                    final int bucket = bucketInfo.mStandbyBucket;
-                    if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE
-                            || bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) {
-                        throw new IllegalArgumentException(
-                                "Cannot set the standby bucket to " + bucket);
-                    }
-                    // Caller cannot set their own standby state
-                    if (mPackageManagerInternal.getPackageUid(packageName,
-                            PackageManager.MATCH_ANY_USER, userId) == callingUid) {
-                        throw new IllegalArgumentException("Cannot set your own standby bucket");
-                    }
-                    mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason,
-                            elapsedRealtime, shellCaller);
-                }
+                mAppStandby.setAppStandbyBuckets(appBuckets.getList(), userId,
+                        callingUid, callingPid);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 198b4c3..bde2cfd 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -611,65 +611,91 @@
 
     int setParameter(UUID modelId, @ModelParams int modelParam, int value) {
         synchronized (mLock) {
-            MetricsLogger.count(mContext, "sth_set_parameter", 1);
-            if (modelId == null || mModule == null) {
-                return SoundTrigger.STATUS_ERROR;
-            }
-            ModelData modelData = mModelDataMap.get(modelId);
-            if (modelData == null) {
-                Slog.w(TAG, "SetParameter: Invalid model id:" + modelId);
-                return SoundTrigger.STATUS_BAD_VALUE;
-            }
-            if (!modelData.isModelLoaded()) {
-                Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelId);
-                return SoundTrigger.STATUS_BAD_VALUE;
-            }
-
-            return mModule.setParameter(modelData.getHandle(), modelParam, value);
+            return setParameterLocked(mModelDataMap.get(modelId), modelParam, value);
         }
     }
 
-    int getParameter(@NonNull UUID modelId, @ModelParams int modelParam)
-            throws UnsupportedOperationException, IllegalArgumentException {
+    int setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value) {
         synchronized (mLock) {
-            MetricsLogger.count(mContext, "sth_get_parameter", 1);
-            if (mModule == null) {
-                throw new UnsupportedOperationException("SoundTriggerModule not initialized");
-            }
-
-            ModelData modelData = mModelDataMap.get(modelId);
-            if (modelData == null) {
-                throw new IllegalArgumentException("Invalid model id:" + modelId);
-            }
-            if (!modelData.isModelLoaded()) {
-                throw new UnsupportedOperationException("Given model is not loaded:" + modelId);
-            }
-
-            return mModule.getParameter(modelData.getHandle(), modelParam);
+            return setParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam, value);
         }
     }
 
+    private int setParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam,
+            int value) {
+        MetricsLogger.count(mContext, "sth_set_parameter", 1);
+        if (mModule == null) {
+            return SoundTrigger.STATUS_NO_INIT;
+        }
+        if (modelData == null || !modelData.isModelLoaded()) {
+            Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelData);
+            return SoundTrigger.STATUS_BAD_VALUE;
+        }
+
+        return mModule.setParameter(modelData.getHandle(), modelParam, value);
+    }
+
+    int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
+        synchronized (mLock) {
+            return getParameterLocked(mModelDataMap.get(modelId), modelParam);
+        }
+    }
+
+    int getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
+        synchronized (mLock) {
+            return getParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
+        }
+    }
+
+    private int getParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam) {
+        MetricsLogger.count(mContext, "sth_get_parameter", 1);
+        if (mModule == null) {
+            throw new UnsupportedOperationException("SoundTriggerModule not initialized");
+        }
+
+        if (modelData == null) {
+            throw new IllegalArgumentException("Invalid model id");
+        }
+        if (!modelData.isModelLoaded()) {
+            throw new UnsupportedOperationException("Given model is not loaded:" + modelData);
+        }
+
+        return mModule.getParameter(modelData.getHandle(), modelParam);
+    }
+
     @Nullable
     ModelParamRange queryParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
         synchronized (mLock) {
-            MetricsLogger.count(mContext, "sth_query_parameter", 1);
-            if (mModule == null) {
-                return null;
-            }
-            ModelData modelData = mModelDataMap.get(modelId);
-            if (modelData == null) {
-                Slog.w(TAG, "queryParameter: Invalid model id:" + modelId);
-                return null;
-            }
-            if (!modelData.isModelLoaded()) {
-                Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelId);
-                return null;
-            }
-
-            return mModule.queryParameter(modelData.getHandle(), modelParam);
+            return queryParameterLocked(mModelDataMap.get(modelId), modelParam);
         }
     }
 
+    @Nullable
+    ModelParamRange queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
+        synchronized (mLock) {
+            return queryParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
+        }
+    }
+
+    @Nullable
+    private ModelParamRange queryParameterLocked(@Nullable ModelData modelData,
+            @ModelParams int modelParam) {
+        MetricsLogger.count(mContext, "sth_query_parameter", 1);
+        if (mModule == null) {
+            return null;
+        }
+        if (modelData == null) {
+            Slog.w(TAG, "queryParameter: Invalid model id");
+            return null;
+        }
+        if (!modelData.isModelLoaded()) {
+            Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelData);
+            return null;
+        }
+
+        return mModule.queryParameter(modelData.getHandle(), modelParam);
+    }
+
     //---- SoundTrigger.StatusListener methods
     @Override
     public void onRecognition(RecognitionEvent event) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
index d05e044..54dffdc 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
@@ -16,17 +16,17 @@
 
 package com.android.server.soundtrigger;
 
+import android.annotation.Nullable;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
-import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
-import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
-import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
-import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
-import android.hardware.soundtrigger.SoundTriggerModule;
+
+import com.android.server.voiceinteraction.VoiceInteractionManagerService;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -71,6 +71,58 @@
     public abstract ModuleProperties getModuleProperties();
 
     /**
+     * Set a model specific {@link ModelParams} with the given value. This
+     * parameter will keep its value for the duration the model is loaded regardless of starting and
+     * stopping recognition. Once the model is unloaded, the value will be lost.
+     * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this
+     * method.
+     *
+     * @param keyphraseId The identifier of the keyphrase for which
+     *        to modify model parameters
+     * @param modelParam   {@link ModelParams}
+     * @param value        Value to set
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+     *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+     *           if API is not supported by HAL
+     */
+    public abstract int setParameter(int keyphraseId, @ModelParams int modelParam, int value);
+
+    /**
+     * Get a model specific {@link ModelParams}. This parameter will keep its value
+     * for the duration the model is loaded regardless of starting and stopping recognition.
+     * Once the model is unloaded, the value will be lost. If the value is not set, a default
+     * value is returned. See ModelParams for parameter default values.
+     * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this
+     * method.
+     *
+     * @param keyphraseId The identifier of the keyphrase for which
+     *        to modify model parameters
+     * @param modelParam   {@link ModelParams}
+     * @return value of parameter
+     * @throws UnsupportedOperationException if hal or model do not support this API.
+     *         queryParameter should be checked first.
+     * @throws IllegalArgumentException if invalid model handle or parameter is passed.
+     *         queryParameter should be checked first.
+     */
+    public abstract int getParameter(int keyphraseId, @ModelParams int modelParam);
+
+    /**
+     * Determine if parameter control is supported for the given model handle.
+     * This method should be checked prior to calling {@link SoundTriggerInternal#setParameter}
+     * or {@link SoundTriggerInternal#getParameter}.
+     *
+     * @param keyphraseId The identifier of the keyphrase for which
+     *        to modify model parameters
+     * @param modelParam {@link ModelParams}
+     * @return supported range of parameter, null if not supported
+     */
+    @Nullable
+    public abstract ModelParamRange queryParameter(int keyphraseId,
+            @ModelParams int modelParam);
+
+    /**
      * Unloads (and stops if running) the given keyphraseId
      */
     public abstract int unloadKeyphraseModel(int keyphaseId);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 68b16f3..e37755b 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -1446,26 +1446,45 @@
         @Override
         public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
                 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
-            if (!isInitialized()) return STATUS_ERROR;
+            if (!isInitialized()) throw new UnsupportedOperationException();
             return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
                     recognitionConfig);
         }
 
         @Override
         public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
-            if (!isInitialized()) return STATUS_ERROR;
+            if (!isInitialized()) throw new UnsupportedOperationException();
             return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
         }
 
         @Override
         public ModuleProperties getModuleProperties() {
-            if (!isInitialized()) return null;
+            if (!isInitialized()) throw new UnsupportedOperationException();
             return mSoundTriggerHelper.getModuleProperties();
         }
 
         @Override
+        public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) {
+            if (!isInitialized()) throw new UnsupportedOperationException();
+            return mSoundTriggerHelper.setKeyphraseParameter(keyphraseId, modelParam, value);
+        }
+
+        @Override
+        public int getParameter(int keyphraseId, @ModelParams int modelParam) {
+            if (!isInitialized()) throw new UnsupportedOperationException();
+            return mSoundTriggerHelper.getKeyphraseParameter(keyphraseId, modelParam);
+        }
+
+        @Override
+        @Nullable
+        public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) {
+            if (!isInitialized()) throw new UnsupportedOperationException();
+            return mSoundTriggerHelper.queryKeyphraseParameter(keyphraseId, modelParam);
+        }
+
+        @Override
         public int unloadKeyphraseModel(int keyphraseId) {
-            if (!isInitialized()) return STATUS_ERROR;
+            if (!isInitialized()) throw new UnsupportedOperationException();
             return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
         }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index e9db31b..6b95f0f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -41,7 +41,9 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.os.Binder;
@@ -157,9 +159,13 @@
         }
     }
 
+    private boolean isSupported(UserInfo user) {
+        return user.isFull();
+    }
+
     @Override
-    public boolean isSupported(UserInfo userInfo) {
-        return userInfo.isFull();
+    public boolean isSupportedUser(TargetUser user) {
+        return isSupported(user.getUserInfo());
     }
 
     @Override
@@ -1084,6 +1090,55 @@
             }
         }
 
+        @Override
+        public int setParameter(IVoiceInteractionService service, int keyphraseId,
+                @ModelParams int modelParam, int value) {
+            // Allow the call if this is the current voice interaction service.
+            synchronized (this) {
+                enforceIsCurrentVoiceInteractionService(service);
+            }
+
+            final long caller = Binder.clearCallingIdentity();
+            try {
+                return mSoundTriggerInternal.setParameter(keyphraseId, modelParam, value);
+            } finally {
+                Binder.restoreCallingIdentity(caller);
+            }
+        }
+
+        @Override
+        public int getParameter(IVoiceInteractionService service, int keyphraseId,
+                @ModelParams int modelParam) {
+            // Allow the call if this is the current voice interaction service.
+            synchronized (this) {
+                enforceIsCurrentVoiceInteractionService(service);
+            }
+
+            final long caller = Binder.clearCallingIdentity();
+            try {
+                return mSoundTriggerInternal.getParameter(keyphraseId, modelParam);
+            } finally {
+                Binder.restoreCallingIdentity(caller);
+            }
+        }
+
+        @Override
+        @Nullable
+        public ModelParamRange queryParameter(IVoiceInteractionService service,
+                int keyphraseId, @ModelParams int modelParam) {
+            // Allow the call if this is the current voice interaction service.
+            synchronized (this) {
+                enforceIsCurrentVoiceInteractionService(service);
+            }
+
+            final long caller = Binder.clearCallingIdentity();
+            try {
+                return mSoundTriggerInternal.queryParameter(keyphraseId, modelParam);
+            } finally {
+                Binder.restoreCallingIdentity(caller);
+            }
+        }
+
         private synchronized void unloadAllKeyphraseModels() {
             for (int i = 0; i < mLoadedKeyphraseIds.size(); i++) {
                 final long caller = Binder.clearCallingIdentity();
diff --git a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java
index b75510b..488ee78 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java
@@ -83,7 +83,7 @@
  * could transition to INTENT_STARTED.
  *
  * <p> If any bad transition happened, the state becomse UNKNOWN. The UNKNOWN state
- * could be * accumulated, because during the UNKNOWN state more IntentStarted may
+ * could be accumulated, because during the UNKNOWN state more IntentStarted may
  * be triggered. To recover from UNKNOWN to INIT, all the accumualted IntentStarted
  * should termniate.
  *
@@ -100,7 +100,7 @@
   @Override
   public void onIntentStarted(@NonNull Intent intent, long timestampNs) {
     if (state == State.UNKNOWN) {
-      Log.e(TAG, "IntentStarted during UNKNOWN." + intent);
+      Log.wtf(TAG, "IntentStarted during UNKNOWN." + intent);
       incAccIntentStartedEvents();
       return;
     }
@@ -110,32 +110,32 @@
         state != State.ACTIVITY_CANCELLED &&
         state != State.ACTIVITY_FINISHED &&
         state != State.REPORT_FULLY_DRAWN) {
-      Log.e(TAG,
+      Log.wtf(TAG,
           String.format("Cannot transition from %s to %s", state, State.INTENT_STARTED));
       incAccIntentStartedEvents();
       incAccIntentStartedEvents();
       return;
     }
 
-    Log.i(TAG, String.format("Tansition from %s to %s", state, State.INTENT_STARTED));
+    Log.i(TAG, String.format("Transition from %s to %s", state, State.INTENT_STARTED));
     state = State.INTENT_STARTED;
   }
 
   @Override
   public void onIntentFailed() {
     if (state == State.UNKNOWN) {
-      Log.e(TAG, "IntentFailed during UNKNOWN.");
+      Log.wtf(TAG, "IntentFailed during UNKNOWN.");
       decAccIntentStartedEvents();
       return;
     }
     if (state != State.INTENT_STARTED) {
-      Log.e(TAG,
+      Log.wtf(TAG,
           String.format("Cannot transition from %s to %s", state, State.INTENT_FAILED));
       incAccIntentStartedEvents();
       return;
     }
 
-    Log.i(TAG, String.format("Tansition from %s to %s", state, State.INTENT_FAILED));
+    Log.i(TAG, String.format("Transition from %s to %s", state, State.INTENT_FAILED));
     state = State.INTENT_FAILED;
   }
 
@@ -143,11 +143,11 @@
   public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity,
       @Temperature int temperature) {
     if (state == State.UNKNOWN) {
-      Log.e(TAG, "onActivityLaunched during UNKNOWN.");
+      Log.wtf(TAG, "onActivityLaunched during UNKNOWN.");
       return;
     }
     if (state != State.INTENT_STARTED) {
-      Log.e(TAG,
+      Log.wtf(TAG,
           String.format("Cannot transition from %s to %s", state, State.ACTIVITY_LAUNCHED));
       incAccIntentStartedEvents();
       return;
@@ -160,12 +160,12 @@
   @Override
   public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) {
     if (state == State.UNKNOWN) {
-      Log.e(TAG, "onActivityLaunchCancelled during UNKNOWN.");
+      Log.wtf(TAG, "onActivityLaunchCancelled during UNKNOWN.");
       decAccIntentStartedEvents();
       return;
     }
     if (state != State.ACTIVITY_LAUNCHED) {
-      Log.e(TAG,
+      Log.wtf(TAG,
           String.format("Cannot transition from %s to %s", state, State.ACTIVITY_CANCELLED));
       incAccIntentStartedEvents();
       return;
@@ -179,13 +179,13 @@
   public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity,
       long timestampNs) {
     if (state == State.UNKNOWN) {
-      Log.e(TAG, "onActivityLaunchFinished during UNKNOWN.");
+      Log.wtf(TAG, "onActivityLaunchFinished during UNKNOWN.");
       decAccIntentStartedEvents();
       return;
     }
 
     if (state != State.ACTIVITY_LAUNCHED) {
-      Log.e(TAG,
+      Log.wtf(TAG,
           String.format("Cannot transition from %s to %s", state, State.ACTIVITY_FINISHED));
       incAccIntentStartedEvents();
       return;
@@ -199,7 +199,7 @@
   public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity,
       long timestampNs) {
     if (state == State.UNKNOWN) {
-      Log.e(TAG, "onReportFullyDrawn during UNKNOWN.");
+      Log.wtf(TAG, "onReportFullyDrawn during UNKNOWN.");
       return;
     }
     if (state == State.INIT) {
@@ -207,7 +207,7 @@
     }
 
     if (state != State.ACTIVITY_FINISHED) {
-      Log.e(TAG,
+      Log.wtf(TAG,
           String.format("Cannot transition from %s to %s", state, State.REPORT_FULLY_DRAWN));
       return;
     }
@@ -230,7 +230,7 @@
   private void incAccIntentStartedEvents() {
     if (accIntentStartedEvents < 0) {
       throw new AssertionError(
-          String.format("The number of unknows cannot be negative"));
+          String.format("The number of unknowns cannot be negative"));
     }
     if (accIntentStartedEvents == 0) {
       state = State.UNKNOWN;
@@ -243,7 +243,7 @@
   private void decAccIntentStartedEvents() {
     if (accIntentStartedEvents <= 0) {
       throw new AssertionError(
-          String.format("The number of unknows cannot be negative"));
+          String.format("The number of unknowns cannot be negative"));
     }
     if(accIntentStartedEvents == 1) {
       state = State.INIT;
diff --git a/telecomm/java/android/telecom/AudioState.java b/telecomm/java/android/telecom/AudioState.java
index 8b8c86b..ea641f8 100644
--- a/telecomm/java/android/telecom/AudioState.java
+++ b/telecomm/java/android/telecom/AudioState.java
@@ -19,7 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 86ad795..a8852a8 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -20,12 +20,11 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 
 import com.android.internal.telecom.IVideoProvider;
diff --git a/telecomm/java/android/telecom/CallerInfo.java b/telecomm/java/android/telecom/CallerInfo.java
index a5d25e2..fb6f994 100644
--- a/telecomm/java/android/telecom/CallerInfo.java
+++ b/telecomm/java/android/telecom/CallerInfo.java
@@ -17,7 +17,7 @@
 package android.telecom;
 
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -41,8 +41,8 @@
 import com.android.i18n.phonenumbers.PhoneNumberUtil;
 import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
 import com.android.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder;
-
 import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.Locale;
 
 
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 8808339..f205ec6 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -21,9 +21,9 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
 import android.app.Notification;
 import android.bluetooth.BluetoothDevice;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Intent;
 import android.hardware.camera2.CameraManager;
 import android.net.Uri;
diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
index 7d4ee76..4f6a9d6 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -16,7 +16,7 @@
 
 package android.telecom;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.net.Uri;
 import android.os.Build;
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index a234bb0..be4e2f4 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -16,22 +16,21 @@
 
 package android.telecom;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.telecom.Call.Details.CallDirection;
 
+import com.android.internal.telecom.IVideoProvider;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-import com.android.internal.telecom.IVideoProvider;
-
 /**
  * Information about a call that is used between InCallService and Telecom.
  * @hide
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 61a639a1..a427ed6 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -17,8 +17,8 @@
 package android.telecom;
 
 import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
 import android.bluetooth.BluetoothDevice;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Bundle;
 import android.util.ArrayMap;
diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java
index eb568e0..e1bcb5f 100644
--- a/telecomm/java/android/telecom/PhoneAccountHandle.java
+++ b/telecomm/java/android/telecom/PhoneAccountHandle.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.os.Build;
 import android.os.Parcel;
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index af3c55a..c4c1e21 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -26,7 +26,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -49,6 +49,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -882,7 +883,8 @@
      */
     public TelecomManager(Context context, ITelecomService telecomServiceImpl) {
         Context appContext = context.getApplicationContext();
-        if (appContext != null) {
+        if (appContext != null && Objects.equals(context.getFeatureId(),
+                appContext.getFeatureId())) {
             mContext = appContext;
         } else {
             mContext = context;
@@ -916,7 +918,7 @@
         try {
             if (isServiceConnected()) {
                 return getTelecomService().getDefaultOutgoingPhoneAccount(uriScheme,
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelecomService#getDefaultOutgoingPhoneAccount", e);
@@ -1113,7 +1115,8 @@
     public List<PhoneAccountHandle> getSelfManagedPhoneAccounts() {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().getSelfManagedPhoneAccounts(mContext.getOpPackageName());
+                return getTelecomService().getSelfManagedPhoneAccounts(mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelecomService#getSelfManagedPhoneAccounts()", e);
@@ -1138,8 +1141,8 @@
             boolean includeDisabledAccounts) {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().getCallCapablePhoneAccounts(
-                        includeDisabledAccounts, mContext.getOpPackageName());
+                return getTelecomService().getCallCapablePhoneAccounts(includeDisabledAccounts,
+                        mContext.getOpPackageName(), mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelecomService#getCallCapablePhoneAccounts(" +
@@ -1442,7 +1445,7 @@
         try {
             if (isServiceConnected()) {
                 return getTelecomService().isVoiceMailNumber(accountHandle, number,
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException calling ITelecomService#isVoiceMailNumber.", e);
@@ -1464,7 +1467,7 @@
         try {
             if (isServiceConnected()) {
                 return getTelecomService().getVoiceMailNumber(accountHandle,
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException calling ITelecomService#hasVoiceMailNumber.", e);
@@ -1485,7 +1488,7 @@
         try {
             if (isServiceConnected()) {
                 return getTelecomService().getLine1Number(accountHandle,
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException calling ITelecomService#getLine1Number.", e);
@@ -1506,7 +1509,8 @@
     public boolean isInCall() {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().isInCall(mContext.getOpPackageName());
+                return getTelecomService().isInCall(mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException calling isInCall().", e);
@@ -1531,7 +1535,8 @@
     public boolean isInManagedCall() {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().isInManagedCall(mContext.getOpPackageName());
+                return getTelecomService().isInManagedCall(mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException calling isInManagedCall().", e);
@@ -1711,7 +1716,8 @@
     public boolean isTtySupported() {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().isTtySupported(mContext.getOpPackageName());
+                return getTelecomService().isTtySupported(mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException attempting to get TTY supported state.", e);
@@ -1735,7 +1741,8 @@
     public @TtyMode int getCurrentTtyMode() {
         try {
             if (isServiceConnected()) {
-                return getTelecomService().getCurrentTtyMode(mContext.getOpPackageName());
+                return getTelecomService().getCurrentTtyMode(mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             }
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException attempting to get the current TTY mode.", e);
@@ -1925,7 +1932,8 @@
         ITelecomService service = getTelecomService();
         if (service != null) {
             try {
-                service.showInCallScreen(showDialpad, mContext.getOpPackageName());
+                service.showInCallScreen(showDialpad, mContext.getOpPackageName(),
+                        mContext.getFeatureId());
             } catch (RemoteException e) {
                 Log.e(TAG, "Error calling ITelecomService#showCallScreen", e);
             }
@@ -1988,7 +1996,7 @@
             }
             try {
                 service.placeCall(address, extras == null ? new Bundle() : extras,
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), mContext.getFeatureId());
             } catch (RemoteException e) {
                 Log.e(TAG, "Error calling ITelecomService#placeCall", e);
             }
diff --git a/telecomm/java/android/telecom/VideoCallImpl.java b/telecomm/java/android/telecom/VideoCallImpl.java
index 4a1aa0a..109e7f8 100644
--- a/telecomm/java/android/telecom/VideoCallImpl.java
+++ b/telecomm/java/android/telecom/VideoCallImpl.java
@@ -16,7 +16,7 @@
 
 package android.telecom;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Handler;
diff --git a/telecomm/java/android/telecom/VideoProfile.java b/telecomm/java/android/telecom/VideoProfile.java
index 64e6ca3..4197f3c 100644
--- a/telecomm/java/android/telecom/VideoProfile.java
+++ b/telecomm/java/android/telecom/VideoProfile.java
@@ -19,7 +19,6 @@
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
-import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 204c37e..c54da6b 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -35,12 +35,13 @@
      *
      * @param showDialpad if true, make the dialpad visible initially.
      */
-    void showInCallScreen(boolean showDialpad, String callingPackage);
+    void showInCallScreen(boolean showDialpad, String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getDefaultOutgoingPhoneAccount
      */
-    PhoneAccountHandle getDefaultOutgoingPhoneAccount(in String uriScheme, String callingPackage);
+    PhoneAccountHandle getDefaultOutgoingPhoneAccount(in String uriScheme, String callingPackage,
+            String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getUserSelectedOutgoingPhoneAccount
@@ -56,12 +57,13 @@
      * @see TelecomServiceImpl#getCallCapablePhoneAccounts
      */
     List<PhoneAccountHandle> getCallCapablePhoneAccounts(
-            boolean includeDisabledAccounts, String callingPackage);
+            boolean includeDisabledAccounts, String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getSelfManagedPhoneAccounts
      */
-    List<PhoneAccountHandle> getSelfManagedPhoneAccounts(String callingPackage);
+    List<PhoneAccountHandle> getSelfManagedPhoneAccounts(String callingPackage,
+            String callingFeatureId);
 
     /**
      * @see TelecomManager#getPhoneAccountsSupportingScheme
@@ -123,17 +125,19 @@
      * @see TelecomServiceImpl#isVoiceMailNumber
      */
     boolean isVoiceMailNumber(in PhoneAccountHandle accountHandle, String number,
-            String callingPackage);
+            String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getVoiceMailNumber
      */
-    String getVoiceMailNumber(in PhoneAccountHandle accountHandle, String callingPackage);
+    String getVoiceMailNumber(in PhoneAccountHandle accountHandle, String callingPackage,
+            String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getLine1Number
      */
-    String getLine1Number(in PhoneAccountHandle accountHandle, String callingPackage);
+    String getLine1Number(in PhoneAccountHandle accountHandle, String callingPackage,
+            String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getDefaultPhoneApp
@@ -172,12 +176,12 @@
     /**
      * @see TelecomServiceImpl#isInCall
      */
-    boolean isInCall(String callingPackage);
+    boolean isInCall(String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#isInManagedCall
      */
-    boolean isInManagedCall(String callingPackage);
+    boolean isInManagedCall(String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#isRinging
@@ -229,12 +233,12 @@
     /**
      * @see TelecomServiceImpl#isTtySupported
      */
-    boolean isTtySupported(String callingPackage);
+    boolean isTtySupported(String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#getCurrentTtyMode
      */
-    int getCurrentTtyMode(String callingPackage);
+    int getCurrentTtyMode(String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#addNewIncomingCall
@@ -249,7 +253,7 @@
     /**
      * @see TelecomServiceImpl#placeCall
      */
-    void placeCall(in Uri handle, in Bundle extras, String callingPackage);
+    void placeCall(in Uri handle, in Bundle extras, String callingPackage, String callingFeatureId);
 
     /**
      * @see TelecomServiceImpl#enablePhoneAccount
diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index eb02ea6f..a17a19c 100644
--- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -18,14 +18,16 @@
 
 import android.annotation.Nullable;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.permission.IPermissionManager;
 import android.provider.Settings;
-import android.telephony.Rlog;
+import android.util.Log;
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -76,15 +78,16 @@
      */
     public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage,
             IPackageManager packageManager, IPermissionManager permissionManager,
-            TelephonyManager telephonyManager, ContentResolver contentResolver, int userId) {
+            TelephonyManager telephonyManager, int userId, Context context) {
         if (DEBUG) {
-            Rlog.d(TAG, "disableCarrierAppsUntilPrivileged");
+            Log.d(TAG, "disableCarrierAppsUntilPrivileged");
         }
         SystemConfig config = SystemConfig.getInstance();
         ArraySet<String> systemCarrierAppsDisabledUntilUsed =
                 config.getDisabledUntilUsedPreinstalledCarrierApps();
         ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
                 config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+        ContentResolver contentResolver = getContentResolverForUser(context, userId);
         disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager,
                 telephonyManager, contentResolver, userId, systemCarrierAppsDisabledUntilUsed,
                 systemCarrierAssociatedAppsDisabledUntilUsed);
@@ -102,10 +105,10 @@
      * Manager can kill it, and this can lead to crashes as the app is in an unexpected state.
      */
     public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage,
-            IPackageManager packageManager, IPermissionManager permissionManager,
-            ContentResolver contentResolver, int userId) {
+            IPackageManager packageManager, IPermissionManager permissionManager, int userId,
+            Context context) {
         if (DEBUG) {
-            Rlog.d(TAG, "disableCarrierAppsUntilPrivileged");
+            Log.d(TAG, "disableCarrierAppsUntilPrivileged");
         }
         SystemConfig config = SystemConfig.getInstance();
         ArraySet<String> systemCarrierAppsDisabledUntilUsed =
@@ -114,15 +117,23 @@
 
         ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
                 config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+        ContentResolver contentResolver = getContentResolverForUser(context, userId);
         disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager,
                 null /* telephonyManager */, contentResolver, userId,
                 systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed);
     }
 
+    private static ContentResolver getContentResolverForUser(Context context, int userId) {
+        Context userContext = context.createContextAsUser(UserHandle.getUserHandleForUid(userId),
+                0);
+        return userContext.getContentResolver();
+    }
+
     /**
      * Disable carrier apps until they are privileged
      * Must be public b/c framework unit tests can't access package-private methods.
      */
+    // Must be public b/c framework unit tests can't access package-private methods.
     @VisibleForTesting
     public static void disableCarrierAppsUntilPrivileged(String callingPackage,
             IPackageManager packageManager, IPermissionManager permissionManager,
@@ -142,9 +153,8 @@
                 systemCarrierAssociatedAppsDisabledUntilUsed);
 
         List<String> enabledCarrierPackages = new ArrayList<>();
-
-        boolean hasRunOnce = Settings.Secure.getIntForUser(
-                contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, userId) == 1;
+        boolean hasRunOnce = Settings.Secure.getInt(contentResolver,
+                Settings.Secure.CARRIER_APPS_HANDLED, 0) == 1;
 
         try {
             for (ApplicationInfo ai : candidates) {
@@ -177,7 +187,7 @@
                             || ai.enabledSetting
                             == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
                             || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) {
-                        Rlog.i(TAG, "Update state(" + packageName + "): ENABLED for user "
+                        Log.i(TAG, "Update state(" + packageName + "): ENABLED for user "
                                 + userId);
                         packageManager.setSystemAppInstallState(
                                 packageName,
@@ -200,7 +210,7 @@
                                     == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
                                     || (associatedApp.flags
                                     & ApplicationInfo.FLAG_INSTALLED) == 0) {
-                                Rlog.i(TAG, "Update associated state(" + associatedApp.packageName
+                                Log.i(TAG, "Update associated state(" + associatedApp.packageName
                                         + "): ENABLED for user " + userId);
                                 packageManager.setSystemAppInstallState(
                                         associatedApp.packageName,
@@ -225,7 +235,7 @@
                             && ai.enabledSetting
                             == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                             && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
-                        Rlog.i(TAG, "Update state(" + packageName
+                        Log.i(TAG, "Update state(" + packageName
                                 + "): DISABLED_UNTIL_USED for user " + userId);
                         packageManager.setSystemAppInstallState(
                                 packageName,
@@ -243,7 +253,7 @@
                                         == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                                         && (associatedApp.flags
                                         & ApplicationInfo.FLAG_INSTALLED) != 0) {
-                                    Rlog.i(TAG,
+                                    Log.i(TAG,
                                             "Update associated state(" + associatedApp.packageName
                                                     + "): DISABLED_UNTIL_USED for user " + userId);
                                     packageManager.setSystemAppInstallState(
@@ -259,8 +269,7 @@
 
             // Mark the execution so we do not disable apps again.
             if (!hasRunOnce) {
-                Settings.Secure.putIntForUser(
-                        contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1, userId);
+                Settings.Secure.putInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1);
             }
 
             if (!enabledCarrierPackages.isEmpty()) {
@@ -271,7 +280,7 @@
                 permissionManager.grantDefaultPermissionsToEnabledCarrierApps(packageNames, userId);
             }
         } catch (RemoteException e) {
-            Rlog.w(TAG, "Could not reach PackageManager", e);
+            Log.w(TAG, "Could not reach PackageManager", e);
         }
     }
 
@@ -393,7 +402,7 @@
                 return ai;
             }
         } catch (RemoteException e) {
-            Rlog.w(TAG, "Could not reach PackageManager", e);
+            Log.w(TAG, "Could not reach PackageManager", e);
         }
         return null;
     }
diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
index 5fb4e90..5c53f7e 100644
--- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
@@ -16,10 +16,10 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
 import android.os.Build;
-import android.telephony.Rlog;
+import android.util.Log;
 import android.text.TextUtils;
 import android.util.SparseIntArray;
 
@@ -498,11 +498,11 @@
         StringBuilder ret = new StringBuilder(lengthSeptets);
 
         if (languageTable < 0 || languageTable > sLanguageTables.length) {
-            Rlog.w(TAG, "unknown language table " + languageTable + ", using default");
+            Log.w(TAG, "unknown language table " + languageTable + ", using default");
             languageTable = 0;
         }
         if (shiftTable < 0 || shiftTable > sLanguageShiftTables.length) {
-            Rlog.w(TAG, "unknown single shift table " + shiftTable + ", using default");
+            Log.w(TAG, "unknown single shift table " + shiftTable + ", using default");
             shiftTable = 0;
         }
 
@@ -512,11 +512,11 @@
             String shiftTableToChar = sLanguageShiftTables[shiftTable];
 
             if (languageTableToChar.isEmpty()) {
-                Rlog.w(TAG, "no language table for code " + languageTable + ", using default");
+                Log.w(TAG, "no language table for code " + languageTable + ", using default");
                 languageTableToChar = sLanguageTables[0];
             }
             if (shiftTableToChar.isEmpty()) {
-                Rlog.w(TAG, "no single shift table for code " + shiftTable + ", using default");
+                Log.w(TAG, "no single shift table for code " + shiftTable + ", using default");
                 shiftTableToChar = sLanguageShiftTables[0];
             }
 
@@ -556,7 +556,7 @@
                 }
             }
         } catch (RuntimeException ex) {
-            Rlog.e(TAG, "Error GSM 7 bit packed: ", ex);
+            Log.e(TAG, "Error GSM 7 bit packed: ", ex);
             return null;
         }
 
@@ -813,7 +813,7 @@
         for (int i = 0; i < sz; i++) {
             char c = s.charAt(i);
             if (c == GSM_EXTENDED_ESCAPE) {
-                Rlog.w(TAG, "countGsmSeptets() string contains Escape character, skipping.");
+                Log.w(TAG, "countGsmSeptets() string contains Escape character, skipping.");
                 continue;
             }
             if (charToLanguageTable.get(c, -1) != -1) {
@@ -892,7 +892,7 @@
         for (int i = 0; i < sz && !lpcList.isEmpty(); i++) {
             char c = s.charAt(i);
             if (c == GSM_EXTENDED_ESCAPE) {
-                Rlog.w(TAG, "countGsmSeptets() string contains Escape character, ignoring!");
+                Log.w(TAG, "countGsmSeptets() string contains Escape character, ignoring!");
                 continue;
             }
             // iterate through enabled locking shift tables
@@ -1496,7 +1496,7 @@
         int numTables = sLanguageTables.length;
         int numShiftTables = sLanguageShiftTables.length;
         if (numTables != numShiftTables) {
-            Rlog.e(TAG, "Error: language tables array length " + numTables +
+            Log.e(TAG, "Error: language tables array length " + numTables +
                     " != shift tables array length " + numShiftTables);
         }
 
@@ -1506,7 +1506,7 @@
 
             int tableLen = table.length();
             if (tableLen != 0 && tableLen != 128) {
-                Rlog.e(TAG, "Error: language tables index " + i +
+                Log.e(TAG, "Error: language tables index " + i +
                         " length " + tableLen + " (expected 128 or 0)");
             }
 
@@ -1524,7 +1524,7 @@
 
             int shiftTableLen = shiftTable.length();
             if (shiftTableLen != 0 && shiftTableLen != 128) {
-                Rlog.e(TAG, "Error: language shift tables index " + i +
+                Log.e(TAG, "Error: language shift tables index " + i +
                         " length " + shiftTableLen + " (expected 128 or 0)");
             }
 
diff --git a/telephony/common/com/android/internal/telephony/HbpcdUtils.java b/telephony/common/com/android/internal/telephony/HbpcdUtils.java
index 2f31942..714f5a6 100644
--- a/telephony/common/com/android/internal/telephony/HbpcdUtils.java
+++ b/telephony/common/com/android/internal/telephony/HbpcdUtils.java
@@ -19,7 +19,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
-import android.telephony.Rlog;
+import android.util.Log;
 
 import com.android.internal.telephony.HbpcdLookup.ArbitraryMccSidMatch;
 import com.android.internal.telephony.HbpcdLookup.MccIdd;
@@ -54,16 +54,16 @@
         if (c2 != null) {
             int c2Counter = c2.getCount();
             if (DBG) {
-                Rlog.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter);
+                Log.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter);
             }
             if (c2Counter == 1) {
                 if (DBG) {
-                    Rlog.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2);
+                    Log.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2);
                 }
                 c2.moveToFirst();
                 tmpMcc = c2.getInt(0);
                 if (DBG) {
-                    Rlog.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc);
+                    Log.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc);
                 }
                 c2.close();
                 return tmpMcc;
@@ -85,18 +85,18 @@
             int c3Counter = c3.getCount();
             if (c3Counter > 0) {
                 if (c3Counter > 1) {
-                    Rlog.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3);
+                    Log.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3);
                 }
-                if (DBG) Rlog.d(LOG_TAG, "Query conflict sid returned the cursor " + c3);
+                if (DBG) Log.d(LOG_TAG, "Query conflict sid returned the cursor " + c3);
                 c3.moveToFirst();
                 tmpMcc = c3.getInt(0);
                 if (DBG) {
-                    Rlog.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc);
+                    Log.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc);
                 }
                 if (!isNitzTimeZone) {
                     // time zone is not accurate, it may get wrong mcc, ignore it.
                     if (DBG) {
-                        Rlog.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc);
+                        Log.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc);
                     }
                     tmpMcc = 0;
                 }
@@ -115,18 +115,18 @@
                 null, null);
         if (c5 != null) {
             if (c5.getCount() > 0) {
-                if (DBG) Rlog.d(LOG_TAG, "Query Range returned the cursor " + c5);
+                if (DBG) Log.d(LOG_TAG, "Query Range returned the cursor " + c5);
                 c5.moveToFirst();
                 tmpMcc = c5.getInt(0);
-                if (DBG) Rlog.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc);
+                if (DBG) Log.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc);
                 c5.close();
                 return tmpMcc;
             }
             c5.close();
         }
-        if (DBG) Rlog.d(LOG_TAG, "SID NOT found in mcc_sid_range.");
+        if (DBG) Log.d(LOG_TAG, "SID NOT found in mcc_sid_range.");
 
-        if (DBG) Rlog.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc =  " + tmpMcc);
+        if (DBG) Log.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc =  " + tmpMcc);
         // If unknown MCC still could not be resolved,
         return tmpMcc;
     }
@@ -135,7 +135,7 @@
      *  Gets country information with given MCC.
     */
     public String getIddByMcc(int mcc) {
-        if (DBG) Rlog.d(LOG_TAG, "Enter getHbpcdInfoByMCC.");
+        if (DBG) Log.d(LOG_TAG, "Enter getHbpcdInfoByMCC.");
         String idd = "";
 
         Cursor c = null;
@@ -145,19 +145,19 @@
                 MccIdd.MCC + "=" + mcc, null, null);
         if (cur != null) {
             if (cur.getCount() > 0) {
-                if (DBG) Rlog.d(LOG_TAG, "Query Idd returned the cursor " + cur);
+                if (DBG) Log.d(LOG_TAG, "Query Idd returned the cursor " + cur);
                 // TODO: for those country having more than 1 IDDs, need more information
                 // to decide which IDD would be used. currently just use the first 1.
                 cur.moveToFirst();
                 idd = cur.getString(0);
-                if (DBG) Rlog.d(LOG_TAG, "IDD = " + idd);
+                if (DBG) Log.d(LOG_TAG, "IDD = " + idd);
 
             }
             cur.close();
         }
         if (c != null) c.close();
 
-        if (DBG) Rlog.d(LOG_TAG, "Exit getHbpcdInfoByMCC.");
+        if (DBG) Log.d(LOG_TAG, "Exit getHbpcdInfoByMCC.");
         return idd;
     }
 }
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index b302589..9b82828 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -40,7 +40,7 @@
 import android.provider.Telephony;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.PackageChangeReceiver;
-import android.telephony.Rlog;
+import android.util.Log;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
@@ -565,7 +565,7 @@
             int mode = appOps.unsafeCheckOp(opStr, applicationData.mUid,
                     applicationData.mPackageName);
             if (mode != AppOpsManager.MODE_ALLOWED) {
-                Rlog.e(LOG_TAG, applicationData.mPackageName + " lost "
+                Log.e(LOG_TAG, applicationData.mPackageName + " lost "
                         + opStr + ": "
                         + (updateIfNeeded ? " (fixing)" : " (no permission to fix)"));
                 if (updateIfNeeded) {
@@ -643,7 +643,7 @@
                     int uid = packageManager.getPackageInfo(oldPackageName, 0).applicationInfo.uid;
                     setExclusiveAppops(oldPackageName, appOps, uid, AppOpsManager.MODE_DEFAULT);
                 } catch (NameNotFoundException e) {
-                    Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName);
+                    Log.w(LOG_TAG, "Old SMS package not found: " + oldPackageName);
                 }
             }
 
@@ -750,7 +750,7 @@
         // the package signature matches system signature.
         final int result = packageManager.checkSignatures(context.getPackageName(), packageName);
         if (result != PackageManager.SIGNATURE_MATCH) {
-            Rlog.e(LOG_TAG, packageName + " does not have system signature");
+            Log.e(LOG_TAG, packageName + " does not have system signature");
             return;
         }
         try {
@@ -758,13 +758,13 @@
             int mode = appOps.unsafeCheckOp(AppOpsManager.OPSTR_WRITE_SMS, info.applicationInfo.uid,
                     packageName);
             if (mode != AppOpsManager.MODE_ALLOWED) {
-                Rlog.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS:  (fixing)");
+                Log.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS:  (fixing)");
                 setExclusiveAppops(packageName, appOps, info.applicationInfo.uid,
                         AppOpsManager.MODE_ALLOWED);
             }
         } catch (NameNotFoundException e) {
             // No whitelisted system app on this device
-            Rlog.e(LOG_TAG, "Package not found: " + packageName);
+            Log.e(LOG_TAG, "Package not found: " + packageName);
         }
 
     }
diff --git a/telephony/common/com/android/internal/telephony/SmsConstants.java b/telephony/common/com/android/internal/telephony/SmsConstants.java
index 19f52b0..3aa8bbf 100644
--- a/telephony/common/com/android/internal/telephony/SmsConstants.java
+++ b/telephony/common/com/android/internal/telephony/SmsConstants.java
@@ -15,7 +15,7 @@
  */
 package com.android.internal.telephony;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * SMS Constants and must be the same as the corresponding
diff --git a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
index 06c08f5..cd365a1 100644
--- a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
+++ b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
@@ -24,13 +24,16 @@
 import android.os.SystemProperties;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
 
 import com.android.internal.telephony.HbpcdLookup.MccIdd;
 import com.android.internal.telephony.HbpcdLookup.MccLookup;
 
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.HashMap;
 
@@ -143,7 +146,7 @@
 
         // First check whether the number is a NANP number.
         int nanpState = checkNANP(numberEntry, allIDDs);
-        if (DBG) Rlog.d(TAG, "NANP type: " + getNumberPlanType(nanpState));
+        if (DBG) Log.d(TAG, "NANP type: " + getNumberPlanType(nanpState));
 
         if ((nanpState == NP_NANP_LOCAL)
             || (nanpState == NP_NANP_AREA_LOCAL)
@@ -173,7 +176,7 @@
 
         int internationalState = checkInternationalNumberPlan(context, numberEntry, allIDDs,
                 NANP_IDD);
-        if (DBG) Rlog.d(TAG, "International type: " + getNumberPlanType(internationalState));
+        if (DBG) Log.d(TAG, "International type: " + getNumberPlanType(internationalState));
         String returnNumber = null;
 
         switch (internationalState) {
@@ -272,7 +275,7 @@
                 }
             }
         } catch (SQLException e) {
-            Rlog.e(TAG, "Can't access HbpcdLookup database", e);
+            Log.e(TAG, "Can't access HbpcdLookup database", e);
         } finally {
             if (cursor != null) {
                 cursor.close();
@@ -281,7 +284,7 @@
 
         IDDS_MAPS.put(mcc, allIDDs);
 
-        if (DBG) Rlog.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs);
+        if (DBG) Log.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs);
         return allIDDs;
     }
 
@@ -472,7 +475,7 @@
                 int tempCC = allCCs[i];
                 for (int j = 0; j < MAX_COUNTRY_CODES_LENGTH; j ++) {
                     if (tempCC == ccArray[j]) {
-                        if (DBG) Rlog.d(TAG, "Country code = " + tempCC);
+                        if (DBG) Log.d(TAG, "Country code = " + tempCC);
                         return tempCC;
                     }
                 }
@@ -509,7 +512,7 @@
                 }
             }
         } catch (SQLException e) {
-            Rlog.e(TAG, "Can't access HbpcdLookup database", e);
+            Log.e(TAG, "Can't access HbpcdLookup database", e);
         } finally {
             if (cursor != null) {
                 cursor.close();
@@ -561,10 +564,10 @@
      * Filter the destination number if using VZW sim card.
      */
     public static String filterDestAddr(Context context, int subId, String destAddr) {
-        if (DBG) Rlog.d(TAG, "enter filterDestAddr. destAddr=\"" + Rlog.pii(TAG, destAddr) + "\"" );
+        if (DBG) Log.d(TAG, "enter filterDestAddr. destAddr=\"" + pii(TAG, destAddr) + "\"" );
 
         if (destAddr == null || !PhoneNumberUtils.isGlobalPhoneNumber(destAddr)) {
-            Rlog.w(TAG, "destAddr" + Rlog.pii(TAG, destAddr) +
+            Log.w(TAG, "destAddr" + pii(TAG, destAddr) +
                     " is not a global phone number! Nothing changed.");
             return destAddr;
         }
@@ -585,9 +588,9 @@
         }
 
         if (DBG) {
-            Rlog.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted."));
-            Rlog.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? Rlog.pii(TAG,
-                    result) : Rlog.pii(TAG, destAddr)) + "\"");
+            Log.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted."));
+            Log.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? pii(TAG,
+                    result) : pii(TAG, destAddr)) + "\"");
         }
         return result != null ? result : destAddr;
     }
@@ -608,7 +611,7 @@
                 networkType = CDMA_HOME_NETWORK;
             }
         } else {
-            if (DBG) Rlog.w(TAG, "warning! unknown mPhoneType value=" + phoneType);
+            if (DBG) Log.w(TAG, "warning! unknown mPhoneType value=" + phoneType);
         }
 
         return networkType;
@@ -650,4 +653,44 @@
         // by default this value is false
         return false;
     }
+
+    /**
+     * Redact personally identifiable information for production users.
+     * @param tag used to identify the source of a log message
+     * @param pii the personally identifiable information we want to apply secure hash on.
+     * @return If tag is loggable in verbose mode or pii is null, return the original input.
+     * otherwise return a secure Hash of input pii
+     */
+    private static String pii(String tag, Object pii) {
+        String val = String.valueOf(pii);
+        if (pii == null || TextUtils.isEmpty(val) || Log.isLoggable(tag, Log.VERBOSE)) {
+            return val;
+        }
+        return "[" + secureHash(val.getBytes()) + "]";
+    }
+
+    /**
+     * Returns a secure hash (using the SHA1 algorithm) of the provided input.
+     *
+     * @return "****" if the build type is user, otherwise the hash
+     * @param input the bytes for which the secure hash should be computed.
+     */
+    private static String secureHash(byte[] input) {
+        // Refrain from logging user personal information in user build.
+        if (android.os.Build.IS_USER) {
+            return "****";
+        }
+
+        MessageDigest messageDigest;
+
+        try {
+            messageDigest = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException e) {
+            return "####";
+        }
+
+        byte[] result = messageDigest.digest(input);
+        return Base64.encodeToString(
+                result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
+    }
 }
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 80a55b2..f6ce0dc 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -28,7 +28,6 @@
 import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
-import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
@@ -521,7 +520,7 @@
             return;
         }
 
-        if (DBG) Rlog.d(LOG_TAG, "No modify permission, check carrier privilege next.");
+        if (DBG) Log.d(LOG_TAG, "No modify permission, check carrier privilege next.");
         enforceCallingOrSelfCarrierPrivilege(context, subId, message);
     }
 
@@ -539,7 +538,7 @@
         }
 
         if (DBG) {
-            Rlog.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next.");
+            Log.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next.");
         }
 
         enforceCallingOrSelfCarrierPrivilege(context, subId, message);
@@ -559,7 +558,7 @@
         }
 
         if (DBG) {
-            Rlog.d(LOG_TAG, "No READ_PRIVILEDED_PHONE_STATE permission, "
+            Log.d(LOG_TAG, "No READ_PRIVILEDED_PHONE_STATE permission, "
                     + "check carrier privilege next.");
         }
 
@@ -584,7 +583,7 @@
             Context context, int subId, int uid, String message) {
         if (getCarrierPrivilegeStatus(context, subId, uid)
                 != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
-            if (DBG) Rlog.e(LOG_TAG, "No Carrier Privilege.");
+            if (DBG) Log.e(LOG_TAG, "No Carrier Privilege.");
             throw new SecurityException(message);
         }
     }
diff --git a/telephony/java/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
similarity index 100%
rename from telephony/java/com/android/internal/telephony/util/TelephonyUtils.java
rename to telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
diff --git a/telephony/java/android/service/carrier/CarrierIdentifier.java b/telephony/java/android/service/carrier/CarrierIdentifier.java
index af5bf74..7957c25 100644
--- a/telephony/java/android/service/carrier/CarrierIdentifier.java
+++ b/telephony/java/android/service/carrier/CarrierIdentifier.java
@@ -20,7 +20,7 @@
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 
 import com.android.internal.telephony.uicc.IccUtils;
diff --git a/telephony/java/android/service/euicc/EuiccProfileInfo.java b/telephony/java/android/service/euicc/EuiccProfileInfo.java
index 6c357cc..8450a90 100644
--- a/telephony/java/android/service/euicc/EuiccProfileInfo.java
+++ b/telephony/java/android/service/euicc/EuiccProfileInfo.java
@@ -19,7 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.service.carrier.CarrierIdentifier;
diff --git a/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java b/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java
index c7a9851..2382f65 100644
--- a/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java
+++ b/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java
@@ -17,7 +17,7 @@
 
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.euicc.DownloadableSubscription;
diff --git a/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java b/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java
index abd4065..d0fb511 100644
--- a/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java
+++ b/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java
@@ -17,7 +17,7 @@
 
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.euicc.DownloadableSubscription;
diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java
index 9753d8b..097041f 100644
--- a/telephony/java/android/telephony/AnomalyReporter.java
+++ b/telephony/java/android/telephony/AnomalyReporter.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.content.Context;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index fdf8849..8668536 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -24,12 +26,11 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.service.carrier.CarrierService;
 import android.telecom.TelecomManager;
 import android.telephony.ims.ImsReasonInfo;
@@ -847,9 +848,12 @@
             "carrier_force_disable_etws_cmas_test_bool";
 
     /**
-     * The default flag specifying whether "Turn on Notifications" option will be always shown in
-     * Settings->More->Emergency broadcasts menu regardless developer options is turned on or not.
+     * The default flag specifying whether "Allow alerts" option will be always shown in
+     * emergency alerts settings regardless developer options is turned on or not.
+     *
+     * @deprecated The allow alerts option is always shown now. No longer need a config for that.
      */
+    @Deprecated
     public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL =
             "always_show_emergency_alert_onoff_bool";
 
@@ -3124,7 +3128,6 @@
      *   EAP-AKA: "0"
      *   EAP-SIM: "1"
      *   EAP-AKA_PRIME: "6"
-     * @hide
      */
     public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool";
 
@@ -3399,12 +3402,17 @@
         /** Prefix of all Ims.KEY_* constants. */
         public static final String KEY_PREFIX = "ims.";
 
-        //TODO: Add configs related to IMS.
+        /**
+         * Delay in milliseconds to turn off wifi when IMS is registered over wifi.
+         */
+        public static final String KEY_WIFI_OFF_DEFERRING_TIME_INT =
+                KEY_PREFIX + "wifi_off_deferring_time_int";
 
         private Ims() {}
 
         private static PersistableBundle getDefaults() {
             PersistableBundle defaults = new PersistableBundle();
+            defaults.putInt(KEY_WIFI_OFF_DEFERRING_TIME_INT, 0);
             return defaults;
         }
     }
@@ -4205,8 +4213,11 @@
     /** @hide */
     @Nullable
     private ICarrierConfigLoader getICarrierConfigLoader() {
-        return ICarrierConfigLoader.Stub
-                .asInterface(ServiceManager.getService(Context.CARRIER_CONFIG_SERVICE));
+        return ICarrierConfigLoader.Stub.asInterface(
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getCarrierConfigServiceRegisterer()
+                        .get());
     }
 
     /**
diff --git a/telephony/java/android/telephony/CbGeoUtils.java b/telephony/java/android/telephony/CbGeoUtils.java
index 84be4e8..719ba8d 100644
--- a/telephony/java/android/telephony/CbGeoUtils.java
+++ b/telephony/java/android/telephony/CbGeoUtils.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.text.TextUtils;
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index ce32dce..3f0aeb5 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -20,10 +20,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.hardware.radio.V1_0.CellInfoType;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.telephony.Rlog;
+
 import java.util.Objects;
 import java.util.UUID;
 
@@ -191,6 +194,7 @@
      *
      * @hide
      */
+    @SystemApi
     public abstract @NonNull CellIdentity sanitizeLocationInfo();
 
     @Override
@@ -321,6 +325,86 @@
     }
 
     /** @hide */
+    public static CellIdentity create(android.hardware.radio.V1_0.CellIdentity cellIdentity) {
+        if (cellIdentity == null)  return null;
+        switch(cellIdentity.cellInfoType) {
+            case CellInfoType.GSM: {
+                if (cellIdentity.cellIdentityGsm.size() == 1) {
+                    return new CellIdentityGsm(cellIdentity.cellIdentityGsm.get(0));
+                }
+                break;
+            }
+            case CellInfoType.WCDMA: {
+                if (cellIdentity.cellIdentityWcdma.size() == 1) {
+                    return new CellIdentityWcdma(cellIdentity.cellIdentityWcdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.TD_SCDMA: {
+                if (cellIdentity.cellIdentityTdscdma.size() == 1) {
+                    return new  CellIdentityTdscdma(cellIdentity.cellIdentityTdscdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.LTE: {
+                if (cellIdentity.cellIdentityLte.size() == 1) {
+                    return new CellIdentityLte(cellIdentity.cellIdentityLte.get(0));
+                }
+                break;
+            }
+            case CellInfoType.CDMA: {
+                if (cellIdentity.cellIdentityCdma.size() == 1) {
+                    return new CellIdentityCdma(cellIdentity.cellIdentityCdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.NONE: break;
+            default: break;
+        }
+        return null;
+    }
+
+    /** @hide */
+    public static CellIdentity create(android.hardware.radio.V1_2.CellIdentity cellIdentity) {
+        if (cellIdentity == null)  return null;
+        switch(cellIdentity.cellInfoType) {
+            case CellInfoType.GSM: {
+                if (cellIdentity.cellIdentityGsm.size() == 1) {
+                    return new CellIdentityGsm(cellIdentity.cellIdentityGsm.get(0));
+                }
+                break;
+            }
+            case CellInfoType.WCDMA: {
+                if (cellIdentity.cellIdentityWcdma.size() == 1) {
+                    return new CellIdentityWcdma(cellIdentity.cellIdentityWcdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.TD_SCDMA: {
+                if (cellIdentity.cellIdentityTdscdma.size() == 1) {
+                    return new  CellIdentityTdscdma(cellIdentity.cellIdentityTdscdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.LTE: {
+                if (cellIdentity.cellIdentityLte.size() == 1) {
+                    return new CellIdentityLte(cellIdentity.cellIdentityLte.get(0));
+                }
+                break;
+            }
+            case CellInfoType.CDMA: {
+                if (cellIdentity.cellIdentityCdma.size() == 1) {
+                    return new CellIdentityCdma(cellIdentity.cellIdentityCdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.NONE: break;
+            default: break;
+        }
+        return null;
+    }
+
+    /** @hide */
     public static CellIdentity create(android.hardware.radio.V1_5.CellIdentity ci) {
         if (ci == null) return null;
         switch (ci.getDiscriminator()) {
diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java
index 2ecdfce..49f425a 100644
--- a/telephony/java/android/telephony/CellIdentityGsm.java
+++ b/telephony/java/android/telephony/CellIdentityGsm.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.telephony.gsm.GsmCellLocation;
 import android.text.TextUtils;
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index 15c9175..bc46550 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
 import android.telephony.gsm.GsmCellLocation;
diff --git a/telephony/java/android/telephony/CellInfoCdma.java b/telephony/java/android/telephony/CellInfoCdma.java
index 2b1387c..acb21f4 100644
--- a/telephony/java/android/telephony/CellInfoCdma.java
+++ b/telephony/java/android/telephony/CellInfoCdma.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
diff --git a/telephony/java/android/telephony/CellInfoGsm.java b/telephony/java/android/telephony/CellInfoGsm.java
index 4f7c7a9..79a9d44 100644
--- a/telephony/java/android/telephony/CellInfoGsm.java
+++ b/telephony/java/android/telephony/CellInfoGsm.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
diff --git a/telephony/java/android/telephony/CellInfoLte.java b/telephony/java/android/telephony/CellInfoLte.java
index 6d19261..fed3ebf 100644
--- a/telephony/java/android/telephony/CellInfoLte.java
+++ b/telephony/java/android/telephony/CellInfoLte.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
diff --git a/telephony/java/android/telephony/CellInfoTdscdma.java b/telephony/java/android/telephony/CellInfoTdscdma.java
index f1305f5..58ff8c9 100644
--- a/telephony/java/android/telephony/CellInfoTdscdma.java
+++ b/telephony/java/android/telephony/CellInfoTdscdma.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
diff --git a/telephony/java/android/telephony/CellInfoWcdma.java b/telephony/java/android/telephony/CellInfoWcdma.java
index ee5fec8..33f6a55 100644
--- a/telephony/java/android/telephony/CellInfoWcdma.java
+++ b/telephony/java/android/telephony/CellInfoWcdma.java
@@ -18,7 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import java.util.Objects;
 
diff --git a/telephony/java/android/telephony/CellLocation.java b/telephony/java/android/telephony/CellLocation.java
index 64776e3..2d0bd52 100644
--- a/telephony/java/android/telephony/CellLocation.java
+++ b/telephony/java/android/telephony/CellLocation.java
@@ -19,7 +19,6 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Bundle;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.telephony.cdma.CdmaCellLocation;
 import android.telephony.gsm.GsmCellLocation;
 
@@ -38,7 +37,11 @@
      */
     public static void requestLocationUpdate() {
         try {
-            ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
+            ITelephony phone = ITelephony.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getTelephonyServiceRegisterer()
+                            .get());
             if (phone != null) {
                 phone.updateServiceLocation();
             }
@@ -50,8 +53,7 @@
     /**
      * Create a new CellLocation from a intent notifier Bundle
      *
-     * This method is used by PhoneStateIntentReceiver and maybe by
-     * external applications.
+     * This method maybe used by external applications.
      *
      * @param bundle Bundle from intent notifier
      * @return newly created CellLocation
diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java
index 1998439..cab3b0c 100644
--- a/telephony/java/android/telephony/CellSignalStrengthCdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java
@@ -20,7 +20,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import java.util.Objects;
 
diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java
index a9f3487..28052aa 100644
--- a/telephony/java/android/telephony/CellSignalStrengthGsm.java
+++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntRange;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index a6ba9c2..2ef2a52 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntRange;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java
index f31fafe..4d67bcf 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.os.Parcel;
@@ -155,7 +157,17 @@
      * @param ss signal strength from modem.
      */
     public CellSignalStrengthNr(android.hardware.radio.V1_4.NrSignalStrength ss) {
-        this(ss.csiRsrp, ss.csiRsrq, ss.csiSinr, ss.ssRsrp, ss.ssRsrq, ss.ssSinr);
+        this(flip(ss.csiRsrp), flip(ss.csiRsrq), ss.csiSinr, flip(ss.ssRsrp), flip(ss.ssRsrq),
+                ss.ssSinr);
+    }
+
+    /**
+     * Flip sign cell strength value when taking in the value from hal
+     * @param val cell strength value
+     * @return flipped value
+     */
+    private static int flip(int val) {
+        return val != CellInfo.UNAVAILABLE ? -val : val;
     }
 
     /**
diff --git a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
index f4a3dbb..3bd9d58 100644
--- a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.os.Parcel;
diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
index 34b1385..535e952 100644
--- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntRange;
 import android.annotation.StringDef;
 import android.compat.annotation.UnsupportedAppUsage;
diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java
index 39af34c..c706d28 100644
--- a/telephony/java/android/telephony/ImsManager.java
+++ b/telephony/java/android/telephony/ImsManager.java
@@ -17,6 +17,8 @@
 package android.telephony.ims;
 
 import android.annotation.NonNull;
+import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -35,7 +37,26 @@
 
     private Context mContext;
 
-    /** @hide */
+    /**
+     * <p>Broadcast Action: Indicates that an IMS operation was rejected by the network due to it
+     * not being authorized on the network.
+     * May include the {@link SubscriptionManager#EXTRA_SUBSCRIPTION_INDEX} extra to also specify
+     * which subscription the operation was rejected for.
+     * <p class="note">
+     * Carrier applications may listen to this broadcast to be notified of possible IMS provisioning
+     * issues.
+     */
+    // Moved from TelephonyIntents, need to keep backwards compatibility with OEM apps that have
+    // this value hard-coded in BroadcastReceiver.
+    @SuppressLint("ActionValue")
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION =
+            "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
+
+    /**
+     * Use {@link Context#getSystemService(String)} to get an instance of this class.
+     * @hide
+     */
     public ImsManager(@NonNull Context context) {
         mContext = context;
     }
diff --git a/telephony/java/android/telephony/NetworkScan.java b/telephony/java/android/telephony/NetworkScan.java
index 202da68..a6dedf7 100644
--- a/telephony/java/android/telephony/NetworkScan.java
+++ b/telephony/java/android/telephony/NetworkScan.java
@@ -16,10 +16,10 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntDef;
-import android.content.Context;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 
 import com.android.internal.telephony.ITelephony;
 
@@ -148,6 +148,9 @@
 
     private ITelephony getITelephony() {
         return ITelephony.Stub.asInterface(
-            ServiceManager.getService(Context.TELEPHONY_SERVICE));
+            TelephonyFrameworkInitializer
+                    .getTelephonyServiceManager()
+                    .getTelephonyServiceRegisterer()
+                    .get());
     }
 }
diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java
index 8c5e107..844289c 100644
--- a/telephony/java/android/telephony/NetworkService.java
+++ b/telephony/java/android/telephony/NetworkService.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
diff --git a/telephony/java/android/telephony/NetworkServiceCallback.java b/telephony/java/android/telephony/NetworkServiceCallback.java
index 89b9665..214ab41 100644
--- a/telephony/java/android/telephony/NetworkServiceCallback.java
+++ b/telephony/java/android/telephony/NetworkServiceCallback.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 6e86a42..2f9e6ac 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 5b12aed..2c62d06 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -2044,4 +2046,27 @@
     public boolean isIwlanPreferred() {
         return mIsIwlanPreferred;
     }
+     /**
+     * @return {@code true}Returns True whenever the modem is searching for service.
+     * To check both CS and PS domain
+     */
+
+    public boolean isSearching() {
+        NetworkRegistrationInfo psRegState = getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        if (psRegState != null && psRegState.getRegistrationState()
+                == NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING) {
+            return true;
+        }
+
+        NetworkRegistrationInfo csRegState = getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        if (csRegState != null && csRegState.getRegistrationState()
+                == NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING) {
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 3350a33..1c58f8f 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -85,8 +87,7 @@
     /**
      * Create a new SignalStrength from a intent notifier Bundle
      *
-     * This method is used by PhoneStateIntentReceiver and maybe by
-     * external applications.
+     * This method may be used by external applications.
      *
      * @param m Bundle from intent notifier
      * @return newly created SignalStrength
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index fbe355e..4f104f4 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -35,7 +35,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -745,7 +744,11 @@
                     "Invalid pdu format. format must be either 3gpp or 3gpp2");
         }
         try {
-            ISms iSms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+            ISms iSms = ISms.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSmsServiceRegisterer()
+                            .get());
             if (iSms != null) {
                 iSms.injectSmsPduForSubscriber(
                         getSubscriptionId(), pdu, format, receivedIntent);
@@ -1535,7 +1538,10 @@
 
     private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(
-                ServiceManager.getService(Context.TELEPHONY_SERVICE));
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getTelephonyServiceRegisterer()
+                        .get());
         if (binder == null) {
             throw new RuntimeException("Could not find Telephony Service.");
         }
@@ -1573,7 +1579,11 @@
     }
 
     private static ISms getISmsService() {
-        return ISms.Stub.asInterface(ServiceManager.getService("isms"));
+        return ISms.Stub.asInterface(
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getSmsServiceRegisterer()
+                        .get());
     }
 
     /**
@@ -2007,7 +2017,11 @@
     public boolean isSMSPromptEnabled() {
         ISms iSms = null;
         try {
-            iSms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+            iSms = ISms.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSmsServiceRegisterer()
+                            .get());
             return iSms.isSMSPromptEnabled();
         } catch (RemoteException ex) {
             return false;
@@ -2434,14 +2448,18 @@
      * @param sentIntent if not NULL this <code>PendingIntent</code> is
      *  broadcast when the message is successfully sent, or failed
      * @throws IllegalArgumentException if contentUri is empty
+     * @deprecated use {@link MmsManager#sendMultimediaMessage} instead.
      */
     public void sendMultimediaMessage(Context context, Uri contentUri, String locationUrl,
             Bundle configOverrides, PendingIntent sentIntent) {
         if (contentUri == null) {
             throw new IllegalArgumentException("Uri contentUri null");
         }
-        MmsManager.getInstance().sendMultimediaMessage(getSubscriptionId(), contentUri,
-                    locationUrl, configOverrides, sentIntent);
+        MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
+        if (m != null) {
+            m.sendMultimediaMessage(getSubscriptionId(), contentUri, locationUrl, configOverrides,
+                    sentIntent);
+        }
     }
 
     /**
@@ -2465,6 +2483,7 @@
      * @param downloadedIntent if not NULL this <code>PendingIntent</code> is
      *  broadcast when the message is downloaded, or the download is failed
      * @throws IllegalArgumentException if locationUrl or contentUri is empty
+     * @deprecated use {@link MmsManager#downloadMultimediaMessage} instead.
      */
     public void downloadMultimediaMessage(Context context, String locationUrl, Uri contentUri,
             Bundle configOverrides, PendingIntent downloadedIntent) {
@@ -2474,8 +2493,11 @@
         if (contentUri == null) {
             throw new IllegalArgumentException("Uri contentUri null");
         }
-        MmsManager.getInstance().downloadMultimediaMessage(getSubscriptionId(), locationUrl,
-                contentUri, configOverrides, downloadedIntent);
+        MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
+        if (m != null) {
+            m.downloadMultimediaMessage(getSubscriptionId(), locationUrl, contentUri,
+                    configOverrides, downloadedIntent);
+        }
     }
 
     // MMS send/download failure result codes
@@ -2517,9 +2539,9 @@
      * </p>
      *
      * @return the bundle key/values pairs that contains MMS configuration values
+     *  or an empty Bundle if they cannot be found.
      */
-    @Nullable
-    public Bundle getCarrierConfigValues() {
+    @NonNull public Bundle getCarrierConfigValues() {
         try {
             ISms iSms = getISmsService();
             if (iSms != null) {
@@ -2528,7 +2550,7 @@
         } catch (RemoteException ex) {
             // ignore it
         }
-        return null;
+        return new Bundle();
     }
 
     /**
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index c0bc29d..c217b8b 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
 
 import android.annotation.Nullable;
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 2c0a1c9..c24eeb7 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 22eed6e..4510fed 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import static android.net.NetworkPolicyManager.OVERRIDE_CONGESTED;
 import static android.net.NetworkPolicyManager.OVERRIDE_UNMETERED;
 
@@ -53,7 +55,6 @@
 import android.os.ParcelUuid;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.provider.Telephony.SimInfo;
 import android.telephony.euicc.EuiccManager;
 import android.telephony.ims.ImsMmTelManager;
@@ -265,7 +266,7 @@
      * <P>Type: TEXT (String)</P>
      */
     /** @hide */
-    public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id";
+    public static final String UNIQUE_KEY_SUBSCRIPTION_ID = SimInfo.UNIQUE_KEY_SUBSCRIPTION_ID;
 
     /**
      * TelephonyProvider column name for a unique identifier for the subscription within the
@@ -274,18 +275,18 @@
      * <P>Type: TEXT (String)</P>
      */
     /** @hide */
-    public static final String ICC_ID = "icc_id";
+    public static final String ICC_ID = SimInfo.ICC_ID;
 
     /**
      * TelephonyProvider column name for user SIM_SlOT_INDEX
      * <P>Type: INTEGER (int)</P>
      */
     /** @hide */
-    public static final String SIM_SLOT_INDEX = "sim_id";
+    public static final String SIM_SLOT_INDEX = SimInfo.SIM_SLOT_INDEX;
 
     /** SIM is not inserted */
     /** @hide */
-    public static final int SIM_NOT_INSERTED = -1;
+    public static final int SIM_NOT_INSERTED = SimInfo.SIM_NOT_INSERTED;
 
     /**
      * The slot-index for Bluetooth Remote-SIM subscriptions
@@ -300,23 +301,7 @@
      * Default value is 0.
      */
     /** @hide */
-    public static final String SUBSCRIPTION_TYPE = "subscription_type";
-
-    /**
-     * TelephonyProvider column name white_listed_apn_data.
-     * It's a bitmask of APN types that will be allowed on this subscription even if it's metered
-     * and mobile data is turned off by the user.
-     * <P>Type: INTEGER (int)</P> For example, if TYPE_MMS is is true, Telephony will allow MMS
-     * data connection to setup even if MMS is metered and mobile_data is turned off on that
-     * subscription.
-     *
-     * Default value is 0.
-     *
-     * @deprecated Replaced by {@link #DATA_ENABLED_OVERRIDE_RULES}
-     * @hide
-     */
-    @Deprecated
-    public static final String WHITE_LISTED_APN_DATA = "white_listed_apn_data";
+    public static final String SUBSCRIPTION_TYPE = SimInfo.SUBSCRIPTION_TYPE;
 
     /**
      * TelephonyProvider column name data_enabled_override_rules.
@@ -329,7 +314,15 @@
      *
      * @hide
      */
-    public static final String DATA_ENABLED_OVERRIDE_RULES = "data_enabled_override_rules";
+    public static final String DATA_ENABLED_OVERRIDE_RULES = SimInfo.DATA_ENABLED_OVERRIDE_RULES;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"SUBSCRIPTION_TYPE_"},
+        value = {
+            SUBSCRIPTION_TYPE_LOCAL_SIM,
+            SUBSCRIPTION_TYPE_REMOTE_SIM})
+    public @interface SubscriptionType {}
 
     /**
      * This constant is to designate a subscription as a Local-SIM Subscription.
@@ -337,7 +330,7 @@
      * device.
      * </p>
      */
-    public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0;
+    public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = SimInfo.SUBSCRIPTION_TYPE_LOCAL_SIM;
 
     /**
      * This constant is to designate a subscription as a Remote-SIM Subscription.
@@ -363,29 +356,21 @@
      * was never seen before.
      * </p>
      */
-    public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1;
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = {"SUBSCRIPTION_TYPE_"},
-        value = {
-            SUBSCRIPTION_TYPE_LOCAL_SIM,
-            SUBSCRIPTION_TYPE_REMOTE_SIM})
-    public @interface SubscriptionType {}
+    public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = SimInfo.SUBSCRIPTION_TYPE_REMOTE_SIM;
 
     /**
      * TelephonyProvider column name for user displayed name.
      * <P>Type: TEXT (String)</P>
      */
     /** @hide */
-    public static final String DISPLAY_NAME = "display_name";
+    public static final String DISPLAY_NAME = SimInfo.DISPLAY_NAME;
 
     /**
      * TelephonyProvider column name for the service provider name for the SIM.
      * <P>Type: TEXT (String)</P>
      */
     /** @hide */
-    public static final String CARRIER_NAME = "carrier_name";
+    public static final String CARRIER_NAME = SimInfo.CARRIER_NAME;
 
     /**
      * Default name resource
@@ -399,44 +384,44 @@
      *
      * @hide
      */
-    public static final String NAME_SOURCE = "name_source";
+    public static final String NAME_SOURCE = SimInfo.NAME_SOURCE;
 
     /**
      * The name_source is the default, which is from the carrier id.
      * @hide
      */
-    public static final int NAME_SOURCE_DEFAULT_SOURCE = 0;
+    public static final int NAME_SOURCE_DEFAULT = SimInfo.NAME_SOURCE_DEFAULT;
 
     /**
      * The name_source is from SIM EF_SPN.
      * @hide
      */
-    public static final int NAME_SOURCE_SIM_SPN = 1;
+    public static final int NAME_SOURCE_SIM_SPN = SimInfo.NAME_SOURCE_SIM_SPN;
 
     /**
      * The name_source is from user input
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    public static final int NAME_SOURCE_USER_INPUT = 2;
+    public static final int NAME_SOURCE_USER_INPUT = SimInfo.NAME_SOURCE_USER_INPUT;
 
     /**
      * The name_source is carrier (carrier app, carrier config, etc.)
      * @hide
      */
-    public static final int NAME_SOURCE_CARRIER = 3;
+    public static final int NAME_SOURCE_CARRIER = SimInfo.NAME_SOURCE_CARRIER;
 
     /**
      * The name_source is from SIM EF_PNN.
      * @hide
      */
-    public static final int NAME_SOURCE_SIM_PNN = 4;
+    public static final int NAME_SOURCE_SIM_PNN = SimInfo.NAME_SOURCE_SIM_PNN;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"NAME_SOURCE_"},
             value = {
-                    NAME_SOURCE_DEFAULT_SOURCE,
+                    NAME_SOURCE_DEFAULT,
                     NAME_SOURCE_SIM_SPN,
                     NAME_SOURCE_USER_INPUT,
                     NAME_SOURCE_CARRIER,
@@ -449,67 +434,30 @@
      * <P>Type: INTEGER (int)</P>
      */
     /** @hide */
-    public static final String COLOR = "color";
-
-    /** @hide */
-    public static final int COLOR_1 = 0;
-
-    /** @hide */
-    public static final int COLOR_2 = 1;
-
-    /** @hide */
-    public static final int COLOR_3 = 2;
-
-    /** @hide */
-    public static final int COLOR_4 = 3;
-
-    /** @hide */
-    public static final int COLOR_DEFAULT = COLOR_1;
+    public static final String COLOR = SimInfo.COLOR;
 
     /**
      * TelephonyProvider column name for the phone number of a SIM.
      * <P>Type: TEXT (String)</P>
      */
     /** @hide */
-    public static final String NUMBER = "number";
+    public static final String NUMBER = SimInfo.NUMBER;
 
     /**
-     * TelephonyProvider column name for the number display format of a SIM.
+     * TelephonyProvider column name for whether data roaming is enabled.
      * <P>Type: INTEGER (int)</P>
      */
     /** @hide */
-    public static final String DISPLAY_NUMBER_FORMAT = "display_number_format";
-
-    /** @hide */
-    public static final int DISPLAY_NUMBER_NONE = 0;
-
-    /** @hide */
-    public static final int DISPLAY_NUMBER_FIRST = 1;
-
-    /** @hide */
-    public static final int DISPLAY_NUMBER_LAST = 2;
-
-    /** @hide */
-    public static final int DISPLAY_NUMBER_DEFAULT = DISPLAY_NUMBER_FIRST;
-
-    /**
-     * TelephonyProvider column name for permission for data roaming of a SIM.
-     * <P>Type: INTEGER (int)</P>
-     */
-    /** @hide */
-    public static final String DATA_ROAMING = "data_roaming";
+    public static final String DATA_ROAMING = SimInfo.DATA_ROAMING;
 
     /** Indicates that data roaming is enabled for a subscription */
-    public static final int DATA_ROAMING_ENABLE = 1;
+    public static final int DATA_ROAMING_ENABLE = SimInfo.DATA_ROAMING_ENABLE;
 
     /** Indicates that data roaming is disabled for a subscription */
-    public static final int DATA_ROAMING_DISABLE = 0;
+    public static final int DATA_ROAMING_DISABLE = SimInfo.DATA_ROAMING_DISABLE;
 
     /** @hide */
-    public static final int DATA_ROAMING_DEFAULT = DATA_ROAMING_DISABLE;
-
-    /** @hide */
-    public static final int SIM_PROVISIONED = 0;
+    public static final int DATA_ROAMING_DEFAULT = SimInfo.DATA_ROAMING_DEFAULT;
 
     /**
      * TelephonyProvider column name for subscription carrier id.
@@ -517,61 +465,61 @@
      * <p>Type: INTEGER (int) </p>
      * @hide
      */
-    public static final String CARRIER_ID = "carrier_id";
+    public static final String CARRIER_ID = SimInfo.CARRIER_ID;
 
     /**
      * @hide A comma-separated list of EHPLMNs associated with the subscription
      * <P>Type: TEXT (String)</P>
      */
-    public static final String EHPLMNS = "ehplmns";
+    public static final String EHPLMNS = SimInfo.EHPLMNS;
 
     /**
      * @hide A comma-separated list of HPLMNs associated with the subscription
      * <P>Type: TEXT (String)</P>
      */
-    public static final String HPLMNS = "hplmns";
+    public static final String HPLMNS = SimInfo.HPLMNS;
 
     /**
      * TelephonyProvider column name for the MCC associated with a SIM, stored as a string.
      * <P>Type: TEXT (String)</P>
      * @hide
      */
-    public static final String MCC_STRING = "mcc_string";
+    public static final String MCC_STRING = SimInfo.MCC_STRING;
 
     /**
      * TelephonyProvider column name for the MNC associated with a SIM, stored as a string.
      * <P>Type: TEXT (String)</P>
      * @hide
      */
-    public static final String MNC_STRING = "mnc_string";
+    public static final String MNC_STRING = SimInfo.MNC_STRING;
 
     /**
      * TelephonyProvider column name for the MCC associated with a SIM.
      * <P>Type: INTEGER (int)</P>
      * @hide
      */
-    public static final String MCC = "mcc";
+    public static final String MCC = SimInfo.MCC;
 
     /**
      * TelephonyProvider column name for the MNC associated with a SIM.
      * <P>Type: INTEGER (int)</P>
      * @hide
      */
-    public static final String MNC = "mnc";
+    public static final String MNC = SimInfo.MNC;
 
     /**
      * TelephonyProvider column name for the iso country code associated with a SIM.
      * <P>Type: TEXT (String)</P>
      * @hide
      */
-    public static final String ISO_COUNTRY_CODE = "iso_country_code";
+    public static final String ISO_COUNTRY_CODE = SimInfo.ISO_COUNTRY_CODE;
 
     /**
      * TelephonyProvider column name for the sim provisioning status associated with a SIM.
      * <P>Type: INTEGER (int)</P>
      * @hide
      */
-    public static final String SIM_PROVISIONING_STATUS = "sim_provisioning_status";
+    public static final String SIM_PROVISIONING_STATUS = SimInfo.SIM_PROVISIONING_STATUS;
 
     /**
      * TelephonyProvider column name for whether a subscription is embedded (that is, present on an
@@ -579,7 +527,7 @@
      * <p>Type: INTEGER (int), 1 for embedded or 0 for non-embedded.
      * @hide
      */
-    public static final String IS_EMBEDDED = "is_embedded";
+    public static final String IS_EMBEDDED = SimInfo.IS_EMBEDDED;
 
     /**
      * TelephonyProvider column name for SIM card identifier. For UICC card it is the ICCID of the
@@ -587,7 +535,7 @@
      * <P>Type: TEXT (String)</P>
      * @hide
      */
-    public static final String CARD_ID = "card_id";
+    public static final String CARD_ID = SimInfo.CARD_ID;
 
     /**
      * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from
@@ -595,7 +543,7 @@
      * <p>TYPE: BLOB
      * @hide
      */
-    public static final String ACCESS_RULES = "access_rules";
+    public static final String ACCESS_RULES = SimInfo.ACCESS_RULES;
 
     /**
      * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from
@@ -605,7 +553,7 @@
      * @hide
      */
     public static final String ACCESS_RULES_FROM_CARRIER_CONFIGS =
-            "access_rules_from_carrier_configs";
+            SimInfo.ACCESS_RULES_FROM_CARRIER_CONFIGS;
 
     /**
      * TelephonyProvider column name identifying whether an embedded subscription is on a removable
@@ -615,79 +563,79 @@
      * <p>TYPE: INTEGER (int), 1 for removable or 0 for non-removable.
      * @hide
      */
-    public static final String IS_REMOVABLE = "is_removable";
+    public static final String IS_REMOVABLE = SimInfo.IS_REMOVABLE;
 
     /**
      *  TelephonyProvider column name for extreme threat in CB settings
      * @hide
      */
-    public static final String CB_EXTREME_THREAT_ALERT = "enable_cmas_extreme_threat_alerts";
+    public static final String CB_EXTREME_THREAT_ALERT = SimInfo.CB_EXTREME_THREAT_ALERT;
 
     /**
      * TelephonyProvider column name for severe threat in CB settings
      *@hide
      */
-    public static final String CB_SEVERE_THREAT_ALERT = "enable_cmas_severe_threat_alerts";
+    public static final String CB_SEVERE_THREAT_ALERT = SimInfo.CB_SEVERE_THREAT_ALERT;
 
     /**
      * TelephonyProvider column name for amber alert in CB settings
      *@hide
      */
-    public static final String CB_AMBER_ALERT = "enable_cmas_amber_alerts";
+    public static final String CB_AMBER_ALERT = SimInfo.CB_AMBER_ALERT;
 
     /**
      * TelephonyProvider column name for emergency alert in CB settings
      *@hide
      */
-    public static final String CB_EMERGENCY_ALERT = "enable_emergency_alerts";
+    public static final String CB_EMERGENCY_ALERT = SimInfo.CB_EMERGENCY_ALERT;
 
     /**
      * TelephonyProvider column name for alert sound duration in CB settings
      *@hide
      */
-    public static final String CB_ALERT_SOUND_DURATION = "alert_sound_duration";
+    public static final String CB_ALERT_SOUND_DURATION = SimInfo.CB_ALERT_SOUND_DURATION;
 
     /**
      * TelephonyProvider column name for alert reminder interval in CB settings
      *@hide
      */
-    public static final String CB_ALERT_REMINDER_INTERVAL = "alert_reminder_interval";
+    public static final String CB_ALERT_REMINDER_INTERVAL = SimInfo.CB_ALERT_REMINDER_INTERVAL;
 
     /**
      * TelephonyProvider column name for enabling vibrate in CB settings
      *@hide
      */
-    public static final String CB_ALERT_VIBRATE = "enable_alert_vibrate";
+    public static final String CB_ALERT_VIBRATE = SimInfo.CB_ALERT_VIBRATE;
 
     /**
      * TelephonyProvider column name for enabling alert speech in CB settings
      *@hide
      */
-    public static final String CB_ALERT_SPEECH = "enable_alert_speech";
+    public static final String CB_ALERT_SPEECH = SimInfo.CB_ALERT_SPEECH;
 
     /**
      * TelephonyProvider column name for ETWS test alert in CB settings
      *@hide
      */
-    public static final String CB_ETWS_TEST_ALERT = "enable_etws_test_alerts";
+    public static final String CB_ETWS_TEST_ALERT = SimInfo.CB_ETWS_TEST_ALERT;
 
     /**
      * TelephonyProvider column name for enable channel50 alert in CB settings
      *@hide
      */
-    public static final String CB_CHANNEL_50_ALERT = "enable_channel_50_alerts";
+    public static final String CB_CHANNEL_50_ALERT = SimInfo.CB_CHANNEL_50_ALERT;
 
     /**
      * TelephonyProvider column name for CMAS test alert in CB settings
      *@hide
      */
-    public static final String CB_CMAS_TEST_ALERT= "enable_cmas_test_alerts";
+    public static final String CB_CMAS_TEST_ALERT = SimInfo.CB_CMAS_TEST_ALERT;
 
     /**
      * TelephonyProvider column name for Opt out dialog in CB settings
      *@hide
      */
-    public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog";
+    public static final String CB_OPT_OUT_DIALOG = SimInfo.CB_OPT_OUT_DIALOG;
 
     /**
      * TelephonyProvider column name for enable Volte.
@@ -696,37 +644,37 @@
      * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
      *@hide
      */
-    public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
+    public static final String ENHANCED_4G_MODE_ENABLED = SimInfo.ENHANCED_4G_MODE_ENABLED;
 
     /**
      * TelephonyProvider column name for enable VT (Video Telephony over IMS)
      *@hide
      */
-    public static final String VT_IMS_ENABLED = "vt_ims_enabled";
+    public static final String VT_IMS_ENABLED = SimInfo.VT_IMS_ENABLED;
 
     /**
      * TelephonyProvider column name for enable Wifi calling
      *@hide
      */
-    public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
+    public static final String WFC_IMS_ENABLED = SimInfo.WFC_IMS_ENABLED;
 
     /**
      * TelephonyProvider column name for Wifi calling mode
      *@hide
      */
-    public static final String WFC_IMS_MODE = "wfc_ims_mode";
+    public static final String WFC_IMS_MODE = SimInfo.WFC_IMS_MODE;
 
     /**
      * TelephonyProvider column name for Wifi calling mode in roaming
      *@hide
      */
-    public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode";
+    public static final String WFC_IMS_ROAMING_MODE = SimInfo.WFC_IMS_ROAMING_MODE;
 
     /**
      * TelephonyProvider column name for enable Wifi calling in roaming
      *@hide
      */
-    public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled";
+    public static final String WFC_IMS_ROAMING_ENABLED = SimInfo.WFC_IMS_ROAMING_ENABLED;
 
     /**
      * TelephonyProvider column name for whether a subscription is opportunistic, that is,
@@ -735,7 +683,7 @@
      * <p>Type: INTEGER (int), 1 for opportunistic or 0 for non-opportunistic.
      * @hide
      */
-    public static final String IS_OPPORTUNISTIC = "is_opportunistic";
+    public static final String IS_OPPORTUNISTIC = SimInfo.IS_OPPORTUNISTIC;
 
     /**
      * TelephonyProvider column name for group ID. Subscriptions with same group ID
@@ -744,7 +692,7 @@
      *
      * @hide
      */
-    public static final String GROUP_UUID = "group_uuid";
+    public static final String GROUP_UUID = SimInfo.GROUP_UUID;
 
     /**
      * TelephonyProvider column name for group owner. It's the package name who created
@@ -752,14 +700,7 @@
      *
      * @hide
      */
-    public static final String GROUP_OWNER = "group_owner";
-
-    /**
-     * TelephonyProvider column name for whether a subscription is metered or not, that is, whether
-     * the network it connects to charges for subscription or not. For example, paid CBRS or unpaid.
-     * @hide
-     */
-    public static final String IS_METERED = "is_metered";
+    public static final String GROUP_OWNER = SimInfo.GROUP_OWNER;
 
     /**
      * TelephonyProvider column name for the profile class of a subscription
@@ -767,7 +708,7 @@
      * <P>Type: INTEGER (int)</P>
      * @hide
      */
-    public static final String PROFILE_CLASS = "profile_class";
+    public static final String PROFILE_CLASS = SimInfo.PROFILE_CLASS;
 
     /**
      * Profile class of the subscription
@@ -775,11 +716,11 @@
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "PROFILE_CLASS_" }, value = {
-            PROFILE_CLASS_TESTING,
-            PROFILE_CLASS_PROVISIONING,
-            PROFILE_CLASS_OPERATIONAL,
-            PROFILE_CLASS_UNSET,
-            PROFILE_CLASS_DEFAULT
+            SimInfo.PROFILE_CLASS_TESTING,
+            SimInfo.PROFILE_CLASS_PROVISIONING,
+            SimInfo.PROFILE_CLASS_OPERATIONAL,
+            SimInfo.PROFILE_CLASS_UNSET,
+            SimInfo.PROFILE_CLASS_DEFAULT
     })
     public @interface ProfileClass {}
 
@@ -791,7 +732,7 @@
      * @hide
      */
     @SystemApi
-    public static final int PROFILE_CLASS_TESTING = 0;
+    public static final int PROFILE_CLASS_TESTING = SimInfo.PROFILE_CLASS_TESTING;
 
     /**
      * A provisioning profile is pre-loaded onto the eUICC and
@@ -800,7 +741,7 @@
      * @hide
      */
     @SystemApi
-    public static final int PROFILE_CLASS_PROVISIONING = 1;
+    public static final int PROFILE_CLASS_PROVISIONING = SimInfo.PROFILE_CLASS_PROVISIONING;
 
     /**
      * An operational profile can be pre-loaded or downloaded
@@ -809,7 +750,7 @@
      * @hide
      */
     @SystemApi
-    public static final int PROFILE_CLASS_OPERATIONAL = 2;
+    public static final int PROFILE_CLASS_OPERATIONAL = SimInfo.PROFILE_CLASS_OPERATIONAL;
 
     /**
      * The profile class is unset. This occurs when profile class
@@ -818,14 +759,14 @@
      * @hide
      */
     @SystemApi
-    public static final int PROFILE_CLASS_UNSET = -1;
+    public static final int PROFILE_CLASS_UNSET = SimInfo.PROFILE_CLASS_UNSET;
 
     /**
      * Default profile class
      * @hide
      */
     @SystemApi
-    public static final int PROFILE_CLASS_DEFAULT = PROFILE_CLASS_UNSET;
+    public static final int PROFILE_CLASS_DEFAULT = SimInfo.PROFILE_CLASS_DEFAULT;
 
     /**
      * IMSI (International Mobile Subscriber Identity).
@@ -833,13 +774,13 @@
      * @hide
      */
     //TODO: add @SystemApi
-    public static final String IMSI = "imsi";
+    public static final String IMSI = SimInfo.IMSI;
 
     /**
      * Whether uicc applications is set to be enabled or disabled. By default it's enabled.
      * @hide
      */
-    public static final String UICC_APPLICATIONS_ENABLED = "uicc_applications_enabled";
+    public static final String UICC_APPLICATIONS_ENABLED = SimInfo.UICC_APPLICATIONS_ENABLED;
 
     /**
      * Broadcast Action: The user has changed one of the default subs related to
@@ -1023,8 +964,11 @@
 
     private final INetworkPolicyManager getNetworkPolicy() {
         if (mNetworkPolicy == null) {
-            mNetworkPolicy = INetworkPolicyManager.Stub
-                    .asInterface(ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
+            mNetworkPolicy = INetworkPolicyManager.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getNetworkPolicyServiceRegisterer()
+                            .get());
         }
         return mNetworkPolicy;
     }
@@ -1041,6 +985,23 @@
      */
     public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
         if (listener == null) return;
+        addOnSubscriptionsChangedListener(listener.mExecutor, listener);
+    }
+
+    /**
+     * Register for changes to the list of active {@link SubscriptionInfo} records or to the
+     * individual records themselves. When a change occurs the onSubscriptionsChanged method of
+     * the listener will be invoked immediately if there has been a notification. The
+     * onSubscriptionChanged method will also be triggered once initially when calling this
+     * function.
+     *
+     * @param listener an instance of {@link OnSubscriptionsChangedListener} with
+     *                 onSubscriptionsChanged overridden.
+     * @param executor the executor that will execute callbacks.
+     */
+    public void addOnSubscriptionsChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnSubscriptionsChangedListener listener) {
         String pkgName = mContext != null ? mContext.getOpPackageName() : "<unknown>";
         if (DBG) {
             logd("register OnSubscriptionsChangedListener pkgName=" + pkgName
@@ -1052,7 +1013,7 @@
                 mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
         if (telephonyRegistryManager != null) {
             telephonyRegistryManager.addOnSubscriptionsChangedListener(listener,
-                    listener.mExecutor);
+                    executor);
         }
     }
 
@@ -1185,7 +1146,11 @@
         SubscriptionInfo subInfo = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 subInfo = iSub.getActiveSubscriptionInfo(subId, mContext.getOpPackageName(),
                         mContext.getFeatureId());
@@ -1219,7 +1184,11 @@
         SubscriptionInfo result = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getActiveSubscriptionInfoForIccId(iccId, mContext.getOpPackageName(),
                         mContext.getFeatureId());
@@ -1253,7 +1222,11 @@
         SubscriptionInfo result = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getActiveSubscriptionInfoForSimSlotIndex(slotIndex,
                         mContext.getOpPackageName(), mContext.getFeatureId());
@@ -1277,7 +1250,11 @@
         List<SubscriptionInfo> result = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getAllSubInfoList(mContext.getOpPackageName(),
                         mContext.getFeatureId());
@@ -1358,7 +1335,11 @@
         List<SubscriptionInfo> activeList = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName(),
                         mContext.getFeatureId());
@@ -1409,7 +1390,11 @@
         List<SubscriptionInfo> result = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getAvailableSubscriptionInfoList(mContext.getOpPackageName(),
                         mContext.getFeatureId());
@@ -1448,7 +1433,11 @@
         List<SubscriptionInfo> result = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getAccessibleSubscriptionInfoList(mContext.getOpPackageName());
             }
@@ -1477,7 +1466,11 @@
     public void requestEmbeddedSubscriptionInfoListRefresh() {
         int cardId = TelephonyManager.from(mContext).getCardIdForDefaultEuicc();
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.requestEmbeddedSubscriptionInfoListRefresh(cardId);
             }
@@ -1506,7 +1499,11 @@
     @SystemApi
     public void requestEmbeddedSubscriptionInfoListRefresh(int cardId) {
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.requestEmbeddedSubscriptionInfoListRefresh(cardId);
             }
@@ -1527,7 +1524,11 @@
         int result = 0;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getAllSubInfoCount(mContext.getOpPackageName(),
                         mContext.getFeatureId());
@@ -1556,7 +1557,11 @@
         int result = 0;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getActiveSubInfoCount(mContext.getOpPackageName(),
                         mContext.getFeatureId());
@@ -1577,7 +1582,11 @@
         int result = 0;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getActiveSubInfoCountMax();
             }
@@ -1634,7 +1643,11 @@
         }
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub == null) {
                 Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- ISub service is null");
                 return;
@@ -1668,7 +1681,11 @@
         }
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub == null) {
                 Log.e(LOG_TAG, "[removeSubscriptionInfoRecord]- ISub service is null");
                 return;
@@ -1771,7 +1788,11 @@
         int result = INVALID_SIM_SLOT_INDEX;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getSlotIndex(subscriptionId);
             }
@@ -1805,7 +1826,11 @@
         int[] subId = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 subId = iSub.getSubId(slotIndex);
             }
@@ -1829,7 +1854,11 @@
         int result = INVALID_PHONE_INDEX;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getPhoneId(subId);
             }
@@ -1863,7 +1892,11 @@
         int subId = INVALID_SUBSCRIPTION_ID;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 subId = iSub.getDefaultSubId();
             }
@@ -1886,7 +1919,11 @@
         int subId = INVALID_SUBSCRIPTION_ID;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 subId = iSub.getDefaultVoiceSubId();
             }
@@ -1916,7 +1953,11 @@
     public void setDefaultVoiceSubscriptionId(int subscriptionId) {
         if (VDBG) logd("setDefaultVoiceSubId sub id = " + subscriptionId);
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.setDefaultVoiceSubId(subscriptionId);
             }
@@ -1964,7 +2005,11 @@
         int subId = INVALID_SUBSCRIPTION_ID;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 subId = iSub.getDefaultSmsSubId();
             }
@@ -1990,7 +2035,11 @@
     public void setDefaultSmsSubId(int subscriptionId) {
         if (VDBG) logd("setDefaultSmsSubId sub id = " + subscriptionId);
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.setDefaultSmsSubId(subscriptionId);
             }
@@ -2028,7 +2077,11 @@
         int subId = INVALID_SUBSCRIPTION_ID;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 subId = iSub.getDefaultDataSubId();
             }
@@ -2054,7 +2107,11 @@
     public void setDefaultDataSubId(int subscriptionId) {
         if (VDBG) logd("setDataSubscription sub id = " + subscriptionId);
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.setDefaultDataSubId(subscriptionId);
             }
@@ -2085,7 +2142,11 @@
     /** @hide */
     public void clearSubscriptionInfo() {
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.clearSubInfo();
             }
@@ -2221,7 +2282,11 @@
      */
     public @NonNull int[] getActiveSubscriptionIdList(boolean visibleOnly) {
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 int[] subId = iSub.getActiveSubIdList(visibleOnly);
                 if (subId != null) return subId;
@@ -2272,7 +2337,11 @@
         int simState = TelephonyManager.SIM_STATE_UNKNOWN;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 simState = iSub.getSimStateForSlotIndex(slotIndex);
             }
@@ -2291,7 +2360,11 @@
      */
     public static void setSubscriptionProperty(int subId, String propKey, String propValue) {
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.setSubscriptionProperty(subId, propKey, propValue);
             }
@@ -2311,7 +2384,11 @@
             Context context) {
         String resultValue = null;
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 resultValue = iSub.getSubscriptionProperty(subId, propKey,
                         context.getOpPackageName(), context.getFeatureId());
@@ -2453,7 +2530,11 @@
     @UnsupportedAppUsage
     public boolean isActiveSubId(int subId) {
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 return iSub.isActiveSubId(subId, mContext.getOpPackageName(),
                         mContext.getFeatureId());
@@ -2756,7 +2837,11 @@
             @TelephonyManager.SetOpportunisticSubscriptionResult Consumer<Integer> callback) {
         if (VDBG) logd("[setPreferredDataSubscriptionId]+ subId:" + subId);
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub == null) return;
 
             ISetOpportunisticDataCallback callbackStub = new ISetOpportunisticDataCallback.Stub() {
@@ -2799,7 +2884,11 @@
     public int getPreferredDataSubscriptionId() {
         int preferredSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 preferredSubId = iSub.getPreferredDataSubscriptionId();
             }
@@ -2830,7 +2919,11 @@
         List<SubscriptionInfo> subInfoList = null;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 subInfoList = iSub.getOpportunisticSubscriptions(contextPkg, contextFeature);
             }
@@ -2931,7 +3024,11 @@
         ParcelUuid groupUuid = null;
         int[] subIdArray = subIdList.stream().mapToInt(i->i).toArray();
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 groupUuid = iSub.createSubscriptionGroup(subIdArray, pkgForDebug);
             } else {
@@ -2981,7 +3078,11 @@
         int[] subIdArray = subIdList.stream().mapToInt(i->i).toArray();
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.addSubscriptionsIntoGroup(subIdArray, groupUuid, pkgForDebug);
             } else {
@@ -3033,7 +3134,11 @@
         int[] subIdArray = subIdList.stream().mapToInt(i->i).toArray();
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.removeSubscriptionsFromGroup(subIdArray, groupUuid, pkgForDebug);
             } else {
@@ -3078,7 +3183,11 @@
 
         List<SubscriptionInfo> result = null;
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = iSub.getSubscriptionsInGroup(groupUuid, contextPkg, contextFeature);
             } else {
@@ -3191,7 +3300,11 @@
             logd("setSubscriptionActivated subId= " + subscriptionId + " enable " + enable);
         }
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 return iSub.setSubscriptionEnabled(enable, subscriptionId);
             }
@@ -3221,7 +3334,11 @@
             logd("setUiccApplicationsEnabled subId= " + subscriptionId + " enable " + enabled);
         }
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 iSub.setUiccApplicationsEnabled(enabled, subscriptionId);
             }
@@ -3251,7 +3368,11 @@
             logd("canDisablePhysicalSubscription");
         }
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 return iSub.canDisablePhysicalSubscription();
             }
@@ -3272,7 +3393,11 @@
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public boolean isSubscriptionEnabled(int subscriptionId) {
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 return iSub.isSubscriptionEnabled(subscriptionId);
             }
@@ -3295,7 +3420,11 @@
         int subId = INVALID_SUBSCRIPTION_ID;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 subId = iSub.getEnabledSubscriptionId(slotIndex);
             }
@@ -3321,7 +3450,11 @@
         int result = 0;
 
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 result = helper.callMethod(iSub);
             }
@@ -3344,7 +3477,11 @@
      */
     public static int getActiveDataSubscriptionId() {
         try {
-            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+            ISub iSub = ISub.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getSubscriptionServiceRegisterer()
+                            .get());
             if (iSub != null) {
                 return iSub.getActiveDataSubscriptionId();
             }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index df8c26d..157d92d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -46,6 +46,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.net.ConnectivityManager;
 import android.net.NetworkStats;
@@ -60,7 +61,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
-import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.WorkSource;
 import android.provider.Settings.SettingNotFoundException;
@@ -107,6 +107,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.SmsApplication;
+import com.android.telephony.Rlog;
 
 import dalvik.system.VMRuntime;
 
@@ -449,12 +450,8 @@
             case UNKNOWN:
                 modemCount = MODEM_COUNT_SINGLE_MODEM;
                 // check for voice and data support, 0 if not supported
-                if (!isVoiceCapable() && !isSmsCapable() && mContext != null) {
-                    ConnectivityManager cm = (ConnectivityManager) mContext
-                            .getSystemService(Context.CONNECTIVITY_SERVICE);
-                    if (cm != null && !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) {
-                        modemCount = MODEM_COUNT_NO_MODEM;
-                    }
+                if (!isVoiceCapable() && !isSmsCapable() && !isDataCapable()) {
+                    modemCount = MODEM_COUNT_NO_MODEM;
                 }
                 break;
             case DSDS:
@@ -785,30 +782,6 @@
     public static final String EXTRA_PRECISE_DISCONNECT_CAUSE = "precise_disconnect_cause";
 
     /**
-     * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast
-     * for an String containing the data APN type.
-     *
-     * <p class="note">
-     * Retrieve with
-     * {@link android.content.Intent#getStringExtra(String name)}.
-     *
-     * @hide
-     */
-    public static final String EXTRA_DATA_APN_TYPE = PhoneConstants.DATA_APN_TYPE_KEY;
-
-    /**
-     * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast
-     * for an String containing the data APN.
-     *
-     * <p class="note">
-     * Retrieve with
-     * {@link android.content.Intent#getStringExtra(String name)}.
-     *
-     * @hide
-     */
-    public static final String EXTRA_DATA_APN = PhoneConstants.DATA_APN_KEY;
-
-    /**
      * Broadcast intent action for letting the default dialer to know to show voicemail
      * notification.
      *
@@ -1471,24 +1444,6 @@
             "android.telephony.extra.SIM_COMBINATION_NAMES";
 
     /**
-     * Broadcast Action: The time was set by the carrier (typically by the NITZ string).
-     * This is a sticky broadcast.
-     * The intent will have the following extra values:</p>
-     * <ul>
-     *   <li><em>time</em> - The time as a long in UTC milliseconds.</li>
-     * </ul>
-     *
-     * <p class="note">
-     * Requires the READ_PHONE_STATE permission.
-     *
-     * <p class="note">This is a protected intent that can only be sent by the system.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String ACTION_NETWORK_SET_TIME = "android.telephony.action.NETWORK_SET_TIME";
-
-    /**
      * <p>Broadcast Action: The emergency callback mode is changed.
      * <ul>
      *   <li><em>phoneinECMState</em> - A boolean value,true=phone in ECM, false=ECM off</li>
@@ -5085,7 +5040,9 @@
      *      not present or not loaded
      * @hide
      */
-    @UnsupportedAppUsage
+    @Nullable
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public String[] getIsimImpu() {
         try {
             IPhoneSubInfo info = getSubscriberInfo();
@@ -5107,7 +5064,11 @@
     @UnsupportedAppUsage
     private IPhoneSubInfo getSubscriberInfo() {
         // get it each time because that process crashes a lot
-        return IPhoneSubInfo.Stub.asInterface(ServiceManager.getService("iphonesubinfo"));
+        return IPhoneSubInfo.Stub.asInterface(
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getPhoneSubServiceRegisterer()
+                        .get());
     }
 
     /**
@@ -5320,11 +5281,19 @@
     }
 
     private ITelephonyRegistry getTelephonyRegistry() {
-        return ITelephonyRegistry.Stub.asInterface(ServiceManager.getService("telephony.registry"));
+        return ITelephonyRegistry.Stub.asInterface(
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getTelephonyRegistryServiceRegisterer()
+                        .get());
     }
 
     private IOns getIOns() {
-        return IOns.Stub.asInterface(ServiceManager.getService("ions"));
+        return IOns.Stub.asInterface(
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getOpportunisticNetworkServiceRegisterer()
+                        .get());
     }
 
     //
@@ -5880,7 +5849,10 @@
      * @param AID Application id. See ETSI 102.221 and 101.220.
      * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID, int p2) {
         return iccOpenLogicalChannel(getSubId(), AID, p2);
     }
@@ -5911,7 +5883,10 @@
      * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public IccOpenLogicalChannelResponse iccOpenLogicalChannel(int subId, String AID, int p2) {
         try {
             ITelephony telephony = getITelephony();
@@ -5939,7 +5914,10 @@
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
     public boolean iccCloseLogicalChannelBySlot(int slotIndex, int channel) {
@@ -5966,7 +5944,10 @@
      * @param channel is the channel id to be closed as returned by a successful
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public boolean iccCloseLogicalChannel(int channel) {
         return iccCloseLogicalChannel(getSubId(), channel);
     }
@@ -5985,7 +5966,10 @@
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public boolean iccCloseLogicalChannel(int subId, int channel) {
         try {
             ITelephony telephony = getITelephony();
@@ -6021,7 +6005,10 @@
      * @return The APDU response from the ICC card with the status appended at the end, or null if
      * there is an issue connecting to the Telephony service.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
     @Nullable
@@ -6059,7 +6046,10 @@
      * @param data Data to be sent with the APDU.
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String iccTransmitApduLogicalChannel(int channel, int cla,
             int instruction, int p1, int p2, int p3, String data) {
         return iccTransmitApduLogicalChannel(getSubId(), channel, cla,
@@ -6088,7 +6078,10 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String iccTransmitApduLogicalChannel(int subId, int channel, int cla,
             int instruction, int p1, int p2, int p3, String data) {
         try {
@@ -6124,7 +6117,10 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
     @NonNull
@@ -6160,7 +6156,10 @@
      * @param data Data to be sent with the APDU.
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String iccTransmitApduBasicChannel(int cla,
             int instruction, int p1, int p2, int p3, String data) {
         return iccTransmitApduBasicChannel(getSubId(), cla,
@@ -6187,7 +6186,10 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String iccTransmitApduBasicChannel(int subId, int cla,
             int instruction, int p1, int p2, int p3, String data) {
         try {
@@ -6215,7 +6217,10 @@
      * @param p3 P3 value of the APDU command.
      * @param filePath
      * @return The APDU response.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public byte[] iccExchangeSimIO(int fileID, int command, int p1, int p2, int p3,
             String filePath) {
         return iccExchangeSimIO(getSubId(), fileID, command, p1, p2, p3, filePath);
@@ -6237,7 +6242,10 @@
      * @param filePath
      * @return The APDU response.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public byte[] iccExchangeSimIO(int subId, int fileID, int command, int p1, int p2,
             int p3, String filePath) {
         try {
@@ -6263,7 +6271,10 @@
      * @return The APDU response from the ICC card in hexadecimal format
      *         with the last 4 bytes being the status word. If the command fails,
      *         returns an empty string.
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String sendEnvelopeWithStatus(String content) {
         return sendEnvelopeWithStatus(getSubId(), content);
     }
@@ -6283,7 +6294,10 @@
      *         with the last 4 bytes being the status word. If the command fails,
      *         returns an empty string.
      * @hide
+     * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
      */
+    // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+    @Deprecated
     public String sendEnvelopeWithStatus(int subId, String content) {
         try {
             ITelephony telephony = getITelephony();
@@ -10636,12 +10650,21 @@
     }
 
     /**
+     * Checks whether cellular data connection is enabled in the device.
+     *
+     * Whether cellular data connection is enabled, meaning upon request whether will try to setup
+     * metered data connection considering all factors below:
+     * 1) User turned on data setting {@link #isDataEnabled}.
+     * 2) Carrier allows data to be on.
+     * 3) Network policy.
+     * And possibly others.
+     *
+     * @return {@code true} if the overall data connection is capable; {@code false} if not.
      * @hide
-     * It's similar to isDataEnabled, but unlike isDataEnabled, this API also evaluates
-     * carrierDataEnabled, policyDataEnabled etc to give a final decision of whether mobile data is
-     * capable of using.
      */
-    public boolean isDataCapable() {
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean isDataConnectionEnabled() {
         boolean retVal = false;
         try {
             int subId = getSubId(SubscriptionManager.getDefaultDataSubscriptionId());
@@ -10649,13 +10672,24 @@
             if (telephony != null)
                 retVal = telephony.isDataEnabled(subId);
         } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#isDataEnabled", e);
+            Log.e(TAG, "Error isDataConnectionEnabled", e);
         } catch (NullPointerException e) {
         }
         return retVal;
     }
 
     /**
+     * Checks if FEATURE_TELEPHONY_DATA is enabled.
+     *
+     * @hide
+     */
+    public boolean isDataCapable() {
+        if (mContext == null) return true;
+        return mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_DATA);
+    }
+
+    /**
      * In this mode, modem will not send specified indications when screen is off.
      * @hide
      */
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index 96b6db7..a1d40e8 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -16,10 +16,11 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import static com.android.internal.util.Preconditions.checkNotNull;
 
 import android.annotation.Nullable;
-import android.content.Context;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -29,7 +30,6 @@
 import android.os.Messenger;
 import android.os.Parcelable;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.util.SparseArray;
 
 import com.android.internal.telephony.ITelephony;
@@ -234,6 +234,9 @@
 
     private ITelephony getITelephony() {
         return ITelephony.Stub.asInterface(
-            ServiceManager.getService(Context.TELEPHONY_SERVICE));
+            TelephonyFrameworkInitializer
+                    .getTelephonyServiceManager()
+                    .getTelephonyServiceRegisterer()
+                    .get());
     }
 }
diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java
index 93ccba1..81a09c6 100644
--- a/telephony/java/android/telephony/UiccAccessRule.java
+++ b/telephony/java/android/telephony/UiccAccessRule.java
@@ -15,6 +15,8 @@
  */
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
diff --git a/telephony/java/android/telephony/VoLteServiceState.java b/telephony/java/android/telephony/VoLteServiceState.java
index 414b999..d4a27d9 100644
--- a/telephony/java/android/telephony/VoLteServiceState.java
+++ b/telephony/java/android/telephony/VoLteServiceState.java
@@ -16,6 +16,8 @@
 
 package android.telephony;
 
+import com.android.telephony.Rlog;
+
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Bundle;
@@ -51,8 +53,7 @@
     /**
      * Create a new VoLteServiceState from a intent notifier Bundle
      *
-     * This method is used by PhoneStateIntentReceiver and maybe by
-     * external applications.
+     * This method is maybe used by external applications.
      *
      * @param m Bundle from intent notifier
      * @return newly created VoLteServiceState
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index dbfb6a2..fab1bf2 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -28,7 +28,7 @@
 import android.provider.Telephony.Carriers;
 import android.telephony.Annotation.ApnType;
 import android.telephony.Annotation.NetworkType;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index 372bdf1..bff12b6 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -31,7 +31,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.telephony.AccessNetworkConstants;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java
index 11dc78a..d33d3f9 100644
--- a/telephony/java/android/telephony/data/DataServiceCallback.java
+++ b/telephony/java/android/telephony/data/DataServiceCallback.java
@@ -22,7 +22,7 @@
 import android.annotation.SystemApi;
 import android.net.LinkProperties;
 import android.os.RemoteException;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.data.DataService.DataServiceProvider;
 
 import java.lang.annotation.Retention;
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index e793979..8220b16 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -28,7 +28,7 @@
 import android.os.RemoteException;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.Annotation.ApnType;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index 1666265..cd3fc95 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -25,7 +25,7 @@
 import android.os.Parcelable;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index 994c49c..e16fffa 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -21,8 +21,8 @@
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.service.euicc.EuiccProfileInfo;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.util.Log;
 
 import com.android.internal.telephony.euicc.IAuthenticateServerCallback;
@@ -148,7 +148,10 @@
 
     private IEuiccCardController getIEuiccCardController() {
         return IEuiccCardController.Stub.asInterface(
-                ServiceManager.getService("euicc_card_controller"));
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getEuiccCardControllerServiceRegisterer()
+                        .get());
     }
 
     /**
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index cb66a96..d5a48df 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -30,7 +30,7 @@
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.TelephonyManager;
 import android.telephony.euicc.EuiccCardManager.ResetOption;
 
@@ -968,6 +968,10 @@
     }
 
     private static IEuiccController getIEuiccController() {
-        return IEuiccController.Stub.asInterface(ServiceManager.getService("econtroller"));
+        return IEuiccController.Stub.asInterface(
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getEuiccControllerService()
+                        .get());
     }
 }
diff --git a/telephony/java/android/telephony/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java
index 8d2049b..abfee61 100644
--- a/telephony/java/android/telephony/ims/ImsConferenceState.java
+++ b/telephony/java/android/telephony/ims/ImsConferenceState.java
@@ -24,7 +24,7 @@
 import android.os.Parcelable;
 import android.telecom.Call;
 import android.telecom.Connection;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.util.Log;
 
 import java.util.HashMap;
diff --git a/telephony/java/android/telephony/ims/ImsExternalCallState.java b/telephony/java/android/telephony/ims/ImsExternalCallState.java
index dcb9c9d..136a83e 100644
--- a/telephony/java/android/telephony/ims/ImsExternalCallState.java
+++ b/telephony/java/android/telephony/ims/ImsExternalCallState.java
@@ -24,7 +24,7 @@
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index 057d22c..91514e9 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -25,14 +25,13 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.content.Context;
 import android.os.Binder;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MmTelFeature;
@@ -1018,7 +1017,10 @@
 
     private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(
-                ServiceManager.getService(Context.TELEPHONY_SERVICE));
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getTelephonyServiceRegisterer()
+                        .get());
         if (binder == null) {
             throw new RuntimeException("Could not find Telephony Service.");
         }
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index d3fb37f..d483291 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -26,8 +26,9 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.telephony.AccessNetworkConstants;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsRcsController;
 import android.telephony.ims.feature.ImsFeature;
@@ -35,6 +36,8 @@
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.Log;
 
+import com.android.internal.telephony.IIntegerConsumer;
+
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
@@ -158,9 +161,20 @@
         if (executor == null) {
             throw new IllegalArgumentException("Must include a non-null Executor.");
         }
+
+        IImsRcsController imsRcsController = getIImsRcsController();
+        if (imsRcsController == null) {
+            Log.e(TAG, "Register registration callback: IImsRcsController is null");
+            throw new ImsException("Cannot find remote IMS service",
+                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
+
         c.setExecutor(executor);
-        throw new UnsupportedOperationException("registerImsRegistrationCallback is not"
-                + "supported.");
+        try {
+            imsRcsController.registerImsRegistrationCallback(mSubId, c.getBinder());
+        } catch (RemoteException | IllegalStateException e) {
+            throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
     }
 
     /**{@inheritDoc}*/
@@ -171,8 +185,18 @@
         if (c == null) {
             throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
         }
-        throw new UnsupportedOperationException("unregisterImsRegistrationCallback is not"
-                + "supported.");
+
+        IImsRcsController imsRcsController = getIImsRcsController();
+        if (imsRcsController == null) {
+            Log.e(TAG, "Unregister registration callback: IImsRcsController is null");
+            throw new IllegalStateException("Cannot find remote IMS service");
+        }
+
+        try {
+            imsRcsController.unregisterImsRegistrationCallback(mSubId, c.getBinder());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
     }
 
     /**{@inheritDoc}*/
@@ -186,8 +210,23 @@
         if (executor == null) {
             throw new IllegalArgumentException("Must include a non-null Executor.");
         }
-        throw new UnsupportedOperationException("getRegistrationState is not"
-                + "supported.");
+
+        IImsRcsController imsRcsController = getIImsRcsController();
+        if (imsRcsController == null) {
+            Log.e(TAG, "Get registration state error: IImsRcsController is null");
+            throw new IllegalStateException("Cannot find remote IMS service");
+        }
+
+        try {
+            imsRcsController.getImsRcsRegistrationState(mSubId, new IIntegerConsumer.Stub() {
+                @Override
+                public void accept(int result) {
+                    executor.execute(() -> stateCallback.accept(result));
+                }
+            });
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
     }
 
     /**{@inheritDoc}*/
@@ -202,10 +241,25 @@
         if (executor == null) {
             throw new IllegalArgumentException("Must include a non-null Executor.");
         }
-        throw new UnsupportedOperationException("getRegistrationTransportType is not"
-                + "supported.");
-    }
 
+        IImsRcsController imsRcsController = getIImsRcsController();
+        if (imsRcsController == null) {
+            Log.e(TAG, "Get registration transport type error: IImsRcsController is null");
+            throw new IllegalStateException("Cannot find remote IMS service");
+        }
+
+        try {
+            imsRcsController.getImsRcsRegistrationTransportType(mSubId,
+                    new IIntegerConsumer.Stub() {
+                        @Override
+                        public void accept(int result) {
+                            executor.execute(() -> transportTypeCallback.accept(result));
+                        }
+                    });
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
 
     /**
      * Registers an {@link AvailabilityCallback} with the system, which will provide RCS
@@ -362,7 +416,10 @@
     }
 
     private IImsRcsController getIImsRcsController() {
-        IBinder binder = ServiceManager.getService(Context.TELEPHONY_IMS_SERVICE);
+        IBinder binder = TelephonyFrameworkInitializer
+                .getTelephonyServiceManager()
+                .getTelephonyImsServiceRegisterer()
+                .get();
         return IImsRcsController.Stub.asInterface(binder);
     }
 }
diff --git a/telephony/java/android/telephony/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java
index 6b72859..2d2e638 100644
--- a/telephony/java/android/telephony/ims/ImsSsData.java
+++ b/telephony/java/android/telephony/ims/ImsSsData.java
@@ -22,7 +22,7 @@
 import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index e4d6335..7ffba7a 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -25,14 +25,13 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.WorkerThread;
-import android.content.Context;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.ims.aidl.IImsConfigCallback;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsConfigImplBase;
@@ -85,6 +84,11 @@
             "STRING_QUERY_RESULT_ERROR_NOT_READY";
 
     /**
+     * There is no existing configuration for the queried provisioning key.
+     */
+    public static final int PROVISIONING_RESULT_UNKNOWN = -1;
+
+    /**
      * The integer result of provisioning for the queried key is disabled.
      */
     public static final int PROVISIONING_VALUE_DISABLED = 0;
@@ -95,6 +99,151 @@
     public static final int PROVISIONING_VALUE_ENABLED = 1;
 
 
+    // Inheriting values from ImsConfig for backwards compatibility.
+    /**
+     * An integer key representing the SIP T1 timer value in milliseconds for the associated
+     * subscription.
+     * <p>
+     * The SIP T1 timer is an estimate of the round-trip time and will retransmit
+     * INVITE transactions that are longer than T1 milliseconds over unreliable transports, doubling
+     * the time before retransmission every time there is no response. See RFC3261, section 17.1.1.1
+     * for more details.
+     * <p>
+     * The value is an integer.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_T1_TIMER_VALUE_MS = 7;
+
+    /**
+     * An integer key representing the voice over LTE (VoLTE) provisioning status for the
+     * associated subscription. Determines whether the user can register for voice services over
+     * LTE.
+     * <p>
+     * Use {@link #PROVISIONING_VALUE_ENABLED} to enable VoLTE provisioning and
+     * {@link #PROVISIONING_VALUE_DISABLED} to disable VoLTE provisioning.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_VOLTE_PROVISIONING_STATUS = 10;
+
+    /**
+     * An integer key representing the video telephony (VT) provisioning status for the
+     * associated subscription. Determines whether the user can register for video services over
+     * LTE.
+     * <p>
+     * Use {@link #PROVISIONING_VALUE_ENABLED} to enable VT provisioning and
+     * {@link #PROVISIONING_VALUE_DISABLED} to disable VT provisioning.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_VT_PROVISIONING_STATUS = 11;
+
+    /**
+     * An integer key associated with the carrier configured SIP PUBLISH timer, which dictates the
+     * expiration time in seconds for published online availability in RCS presence.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15;
+
+    /**
+     * An integer key associated with the carrier configured expiration time in seconds for
+     * RCS presence published offline availability in RCS presence.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16;
+
+    /**
+     * An integer key associated with whether or not capability discovery is provisioned for this
+     * subscription. Any capability requests will be ignored by the RCS service.
+     * <p>
+     * The value is an integer, either {@link #PROVISIONING_VALUE_DISABLED} if capability
+     * discovery is disabled or {@link #PROVISIONING_VALUE_ENABLED} if capability discovery is
+     * enabled.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17;
+
+    /**
+     * An integer key associated with the period of time the capability information of each contact
+     * is cached on the device.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18;
+
+    /**
+     * An integer key associated with the period of time in seconds that the availability
+     * information of a contact is cached on the device.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19;
+
+    /**
+     * An integer key associated with the carrier configured interval in seconds expected between
+     * successive capability polling attempts.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20;
+
+    /**
+     * An integer key representing the minimum time allowed between two consecutive presence publish
+     * messages from the device.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21;
+
+    /**
+     * An integer key associated with the maximum number of MDNs contained in one SIP Request
+     * Contained List (RCS) used to retrieve the RCS capabilities of the contacts book.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22;
+
+    /**
+     * An integer associated with the expiration timer used duriing the SIP subscription of a
+     * Request Contained List (RCL), which is used to retrieve the RCS capabilities of the contact
+     * book.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23;
+
+    /**
+     * An integer key representing the RCS enhanced address book (EAB) provisioning status for the
+     * associated subscription. Determines whether or not SIP OPTIONS or presence will be used to
+     * retrieve RCS capabilities for the user's contacts.
+     * <p>
+     * Use {@link #PROVISIONING_VALUE_ENABLED} to enable EAB provisioning and
+     * {@link #PROVISIONING_VALUE_DISABLED} to disable EAB provisioning.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_EAB_PROVISIONING_STATUS = 25;
+
     /**
      * Override the user-defined WiFi Roaming enabled setting for this subscription, defined in
      * {@link SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, for the purposes of provisioning
@@ -270,7 +419,7 @@
      *
      * @param key An integer that represents the provisioning key, which is defined by the OEM.
      * @return an integer value for the provided key, or
-     * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist.
+     * {@link #PROVISIONING_RESULT_UNKNOWN} if the key doesn't exist.
      * @throws IllegalArgumentException if the key provided was invalid.
      */
     @WorkerThread
@@ -415,7 +564,11 @@
     }
 
     private static boolean isImsAvailableOnDevice() {
-        IPackageManager pm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+        IPackageManager pm = IPackageManager.Stub.asInterface(
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getPackageManagerServiceRegisterer()
+                        .get());
         if (pm == null) {
             // For some reason package manger is not available.. This will fail internally anyways,
             // so do not throw error and allow.
@@ -432,7 +585,10 @@
 
     private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(
-                ServiceManager.getService(Context.TELEPHONY_SERVICE));
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getTelephonyServiceRegisterer()
+                        .get());
         if (binder == null) {
             throw new RuntimeException("Could not find Telephony Service.");
         }
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index 492170b..893a311 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -35,6 +36,7 @@
  * Contains the User Capability Exchange capabilities corresponding to a contact's URI.
  * @hide
  */
+@SystemApi
 public final class RcsContactUceCapability implements Parcelable {
 
     /** Supports 1-to-1 chat */
@@ -135,7 +137,7 @@
          * @param type The capability to map to a service URI that is different from the contact's
          *         URI.
          */
-        public Builder add(@CapabilityFlag int type, @NonNull Uri serviceUri) {
+        public @NonNull Builder add(@CapabilityFlag int type, @NonNull Uri serviceUri) {
             mCapabilities.mCapabilities |= type;
             // Put each of these capabilities into the map separately.
             for (int shift = 0; shift < Integer.SIZE; shift++) {
@@ -157,7 +159,7 @@
          * Add a UCE capability flag that this contact supports.
          * @param type the capability that the contact supports.
          */
-        public Builder add(@CapabilityFlag int type) {
+        public @NonNull Builder add(@CapabilityFlag int type) {
             mCapabilities.mCapabilities |= type;
             return this;
         }
@@ -167,7 +169,7 @@
          * @param extension A string containing a carrier specific service tag that is an extension
          *         of the {@link CapabilityFlag}s that are defined here.
          */
-        public Builder add(@NonNull String extension) {
+        public @NonNull Builder add(@NonNull String extension) {
             mCapabilities.mExtensionTags.add(extension);
             return this;
         }
@@ -175,7 +177,7 @@
         /**
          * @return the constructed instance.
          */
-        public RcsContactUceCapability build() {
+        public @NonNull RcsContactUceCapability build() {
             return mCapabilities;
         }
     }
@@ -205,7 +207,7 @@
         }
     }
 
-    public static final Creator<RcsContactUceCapability> CREATOR =
+    public static final @NonNull Creator<RcsContactUceCapability> CREATOR =
             new Creator<RcsContactUceCapability>() {
         @Override
         public RcsContactUceCapability createFromParcel(Parcel in) {
@@ -219,7 +221,7 @@
     };
 
     @Override
-    public void writeToParcel(Parcel out, int flags) {
+    public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeParcelable(mContactUri, 0);
         out.writeInt(mCapabilities);
         out.writeStringList(mExtensionTags);
diff --git a/telephony/java/android/telephony/ims/RcsControllerCall.java b/telephony/java/android/telephony/ims/RcsControllerCall.java
index ce03c3c..1e93437 100644
--- a/telephony/java/android/telephony/ims/RcsControllerCall.java
+++ b/telephony/java/android/telephony/ims/RcsControllerCall.java
@@ -18,7 +18,7 @@
 
 import android.content.Context;
 import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.ims.aidl.IRcsMessage;
 
 /**
@@ -35,8 +35,11 @@
     }
 
     <R> R call(RcsServiceCall<R> serviceCall) throws RcsMessageStoreException {
-        IRcsMessage iRcsMessage = IRcsMessage.Stub.asInterface(ServiceManager.getService(
-                Context.TELEPHONY_RCS_MESSAGE_SERVICE));
+        IRcsMessage iRcsMessage = IRcsMessage.Stub.asInterface(
+                TelephonyFrameworkInitializer
+                        .getTelephonyServiceManager()
+                        .getTelephonyRcsMessageServiceRegisterer()
+                        .get());
         if (iRcsMessage == null) {
             throw new RcsMessageStoreException("Could not connect to RCS storage service");
         }
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 75e3f0a..2e3f59a 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -21,12 +21,11 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
-import android.content.Context;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.ims.aidl.IImsRcsController;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
 import android.util.Log;
@@ -365,7 +364,10 @@
     }
 
     private IImsRcsController getIImsRcsController() {
-        IBinder binder = ServiceManager.getService(Context.TELEPHONY_IMS_SERVICE);
+        IBinder binder = TelephonyFrameworkInitializer
+                .getTelephonyServiceManager()
+                .getTelephonyImsServiceRegisterer()
+                .get();
         return IImsRcsController.Stub.asInterface(binder);
     }
 }
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
index e81bac0..6f6aa44 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
@@ -19,6 +19,9 @@
 import android.net.Uri;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
+import android.telephony.ims.aidl.IImsRegistrationCallback;
+
+import com.android.internal.telephony.IIntegerConsumer;
 
 /**
  * Interface used to interact with the Telephony IMS.
@@ -26,6 +29,13 @@
  * {@hide}
  */
 interface IImsRcsController {
+    // IMS RCS registration commands
+    void registerImsRegistrationCallback(int subId, IImsRegistrationCallback c);
+    void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback c);
+    void getImsRcsRegistrationState(int subId, IIntegerConsumer consumer);
+    void getImsRcsRegistrationTransportType(int subId, IIntegerConsumer consumer);
+
+    // IMS RCS capability commands
     void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback c);
     void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback c);
     boolean isCapable(int subId, int capability, int radioTech);
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index d18e93c..60cf216 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
+import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.aidl.IImsConfig;
 import android.telephony.ims.aidl.IImsConfigCallback;
 import android.util.Log;
@@ -228,7 +229,8 @@
      * The configuration requested resulted in an unknown result. This may happen if the
      * IMS configurations are unavailable.
      */
-    public static final int CONFIG_RESULT_UNKNOWN = -1;
+    public static final int CONFIG_RESULT_UNKNOWN = ProvisioningManager.PROVISIONING_RESULT_UNKNOWN;
+
     /**
      * Setting the configuration value completed.
      */
diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java
index cfc803c..0d86e2b 100644
--- a/telephony/java/com/android/ims/ImsConfig.java
+++ b/telephony/java/com/android/ims/ImsConfig.java
@@ -19,7 +19,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.aidl.IImsConfig;
@@ -178,7 +178,7 @@
          * SIP T1 timer value in milliseconds. See RFC 3261 for define.
          * Value is in Integer format.
          */
-        public static final int SIP_T1_TIMER = 7;
+        public static final int SIP_T1_TIMER = ProvisioningManager.KEY_T1_TIMER_VALUE_MS;
 
         /**
          * SIP T2 timer value in milliseconds.  See RFC 3261 for define.
@@ -196,13 +196,15 @@
          * VoLTE status for VLT/s status of Enabled (1), or Disabled (0).
          * Value is in Integer format.
          */
-        public static final int VLT_SETTING_ENABLED = 10;
+        public static final int VLT_SETTING_ENABLED =
+                ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS;
 
         /**
          * VoLTE status for LVC/s status of Enabled (1), or Disabled (0).
          * Value is in Integer format.
          */
-        public static final int LVC_SETTING_ENABLED = 11;
+        public static final int LVC_SETTING_ENABLED =
+                ProvisioningManager.KEY_VT_PROVISIONING_STATUS;
         /**
          * Domain Name for the device to populate the request URI for REGISTRATION.
          * Value is in String format.
@@ -222,48 +224,56 @@
          * Requested expiration for Published Online availability.
          * Value is in Integer format.
          */
-        public static final int PUBLISH_TIMER = 15;
+        public static final int PUBLISH_TIMER = ProvisioningManager.KEY_RCS_PUBLISH_TIMER_SEC;
         /**
          * Requested expiration for Published Offline availability.
          * Value is in Integer format.
          */
-        public static final int PUBLISH_TIMER_EXTENDED = 16;
+        public static final int PUBLISH_TIMER_EXTENDED =
+                ProvisioningManager.KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC;
         /**
          *
          * Value is in Integer format.
          */
-        public static final int CAPABILITY_DISCOVERY_ENABLED = 17;
+        public static final int CAPABILITY_DISCOVERY_ENABLED =
+                ProvisioningManager.KEY_RCS_CAPABILITY_DISCOVERY_ENABLED;
         /**
          * Period of time the capability information of the  contact is cached on handset.
          * Value is in Integer format.
          */
-        public static final int CAPABILITIES_CACHE_EXPIRATION = 18;
+        public static final int CAPABILITIES_CACHE_EXPIRATION =
+                ProvisioningManager.KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC;
         /**
          * Peiod of time the availability information of a contact is cached on device.
          * Value is in Integer format.
          */
-        public static final int AVAILABILITY_CACHE_EXPIRATION = 19;
+        public static final int AVAILABILITY_CACHE_EXPIRATION =
+                ProvisioningManager.KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC;
         /**
          * Interval between successive capabilities polling.
          * Value is in Integer format.
          */
-        public static final int CAPABILITIES_POLL_INTERVAL = 20;
+        public static final int CAPABILITIES_POLL_INTERVAL =
+                ProvisioningManager.KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC;
         /**
          * Minimum time between two published messages from the device.
          * Value is in Integer format.
          */
-        public static final int SOURCE_THROTTLE_PUBLISH = 21;
+        public static final int SOURCE_THROTTLE_PUBLISH =
+                ProvisioningManager.KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS;
         /**
          * The Maximum number of MDNs contained in one Request Contained List.
          * Value is in Integer format.
          */
-        public static final int MAX_NUMENTRIES_IN_RCL = 22;
+        public static final int MAX_NUMENTRIES_IN_RCL =
+                ProvisioningManager.KEY_RCS_MAX_NUM_ENTRIES_IN_RCL;
         /**
          * Expiration timer for subscription of a Request Contained List, used in capability
          * polling.
          * Value is in Integer format.
          */
-        public static final int CAPAB_POLL_LIST_SUB_EXP = 23;
+        public static final int CAPAB_POLL_LIST_SUB_EXP =
+                ProvisioningManager.KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC;
         /**
          * Applies compression to LIST Subscription.
          * Value is in Integer format. Enable (1), Disable(0).
@@ -273,7 +283,8 @@
          * VOLTE Status for EAB/s status of Enabled (1), or Disabled (0).
          * Value is in Integer format.
          */
-        public static final int EAB_SETTING_ENABLED = 25;
+        public static final int EAB_SETTING_ENABLED =
+                ProvisioningManager.KEY_EAB_PROVISIONING_STATUS;
         /**
          * Wi-Fi calling roaming status.
          * Value is in Integer format. ON (1), OFF(0).
diff --git a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
index 1d6ec2d..8e86ff7 100644
--- a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
+++ b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
@@ -19,7 +19,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.util.SparseIntArray;
 
 import com.android.internal.telephony.cdma.sms.UserData;
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index f78c65f..1fbe8cb 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -19,6 +19,7 @@
 import android.content.Intent;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsManager;
 
 /**
  * The intents that the telephony services broadcast.
@@ -123,32 +124,6 @@
     public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED
             = TelephonyManager.ACTION_EMERGENCY_CALL_STATE_CHANGED;
 
-    /**
-     * Broadcast Action: The phone's signal strength has changed. The intent will have the
-     * following extra values:</p>
-     * <ul>
-     *   <li><em>phoneName</em> - A string version of the phone name.</li>
-     *   <li><em>asu</em> - A numeric value for the signal strength.
-     *          An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu).
-     *          The following special values are defined:
-     *          <ul><li>0 means "-113 dBm or less".</li><li>31 means "-51 dBm or greater".</li></ul>
-     *   </li>
-     * </ul>
-     *
-     * <p class="note">
-     * You can <em>not</em> receive this through components declared
-     * in manifests, only by exlicitly registering for it with
-     * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver,
-     * android.content.IntentFilter) Context.registerReceiver()}.
-     *
-     * <p class="note">
-     * Requires the READ_PHONE_STATE permission.
-     *
-     * <p class="note">This is a protected intent that can only be sent
-     * by the system.
-     */
-    public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR";
-
 
     /**
      * Broadcast Action: The data connection state has changed for any one of the
@@ -224,9 +199,11 @@
      * <p class="note">
      * This is for the OEM applications to understand about possible provisioning issues.
      * Used in OMA-DM applications.
+     * @deprecated Use {@link ImsManager#ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION} instead.
      */
-    public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION
-            = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
+    @Deprecated
+    public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION =
+            ImsManager.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION;
 
     /**
      * Broadcast Action: A "secret code" has been entered in the dialer. Secret codes are
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index e75c593..832502c 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -20,7 +20,7 @@
 import android.content.res.Resources;
 import android.sysprop.TelephonyProperties;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.SmsCbLocation;
 import android.telephony.SmsCbMessage;
 import android.telephony.cdma.CdmaSmsCbProgramData;
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
index b5af646..cbf0f5c 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
@@ -18,7 +18,7 @@
 
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.telephony.SmsCbCmasInfo;
 import android.telephony.cdma.CdmaSmsCbProgramData;
 import android.telephony.cdma.CdmaSmsCbProgramResults;
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index 0681dc1..417aafd 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -28,7 +28,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
 import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.EncodeException;
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index eed9a86..0dc7401 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -21,7 +21,7 @@
 import android.content.res.Resources.NotFoundException;
 import android.graphics.Bitmap;
 import android.graphics.Color;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.GsmAlphabet;
diff --git a/telephony/java/com/android/telephony/Rlog.java b/telephony/java/com/android/telephony/Rlog.java
new file mode 100644
index 0000000..9d6c930
--- /dev/null
+++ b/telephony/java/com/android/telephony/Rlog.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.telephony;
+
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import com.android.internal.telephony.util.TelephonyUtils;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * A copy of {@link android.telephony.Rlog} to be used within the telephony mainline module.
+ *
+ * @hide
+ */
+public final class Rlog {
+
+    private static final boolean USER_BUILD = TelephonyUtils.IS_USER;
+
+    private Rlog() {
+    }
+
+    private static int log(int priority, String tag, String msg) {
+        return Log.logToRadioBuffer(priority, tag, msg);
+    }
+
+    public static int v(String tag, String msg) {
+        return log(Log.VERBOSE, tag, msg);
+    }
+
+    public static int v(String tag, String msg, Throwable tr) {
+        return log(Log.VERBOSE, tag,
+                msg + '\n' + Log.getStackTraceString(tr));
+    }
+
+    public static int d(String tag, String msg) {
+        return log(Log.DEBUG, tag, msg);
+    }
+
+    public static int d(String tag, String msg, Throwable tr) {
+        return log(Log.DEBUG, tag,
+                msg + '\n' + Log.getStackTraceString(tr));
+    }
+
+    public static int i(String tag, String msg) {
+        return log(Log.INFO, tag, msg);
+    }
+
+    public static int i(String tag, String msg, Throwable tr) {
+        return log(Log.INFO, tag,
+                msg + '\n' + Log.getStackTraceString(tr));
+    }
+
+    public static int w(String tag, String msg) {
+        return log(Log.WARN, tag, msg);
+    }
+
+    public static int w(String tag, String msg, Throwable tr) {
+        return log(Log.WARN, tag,
+                msg + '\n' + Log.getStackTraceString(tr));
+    }
+
+    public static int w(String tag, Throwable tr) {
+        return log(Log.WARN, tag, Log.getStackTraceString(tr));
+    }
+
+    public static int e(String tag, String msg) {
+        return log(Log.ERROR, tag, msg);
+    }
+
+    public static int e(String tag, String msg, Throwable tr) {
+        return log(Log.ERROR, tag,
+                msg + '\n' + Log.getStackTraceString(tr));
+    }
+
+    public static int println(int priority, String tag, String msg) {
+        return log(priority, tag, msg);
+    }
+
+    public static boolean isLoggable(String tag, int level) {
+        return Log.isLoggable(tag, level);
+    }
+
+    /**
+     * Redact personally identifiable information for production users.
+     * @param tag used to identify the source of a log message
+     * @param pii the personally identifiable information we want to apply secure hash on.
+     * @return If tag is loggable in verbose mode or pii is null, return the original input.
+     * otherwise return a secure Hash of input pii
+     */
+    public static String pii(String tag, Object pii) {
+        String val = String.valueOf(pii);
+        if (pii == null || TextUtils.isEmpty(val) || isLoggable(tag, Log.VERBOSE)) {
+            return val;
+        }
+        return "[" + secureHash(val.getBytes()) + "]";
+    }
+
+    /**
+     * Redact personally identifiable information for production users.
+     * @param enablePiiLogging set when caller explicitly want to enable sensitive logging.
+     * @param pii the personally identifiable information we want to apply secure hash on.
+     * @return If enablePiiLogging is set to true or pii is null, return the original input.
+     * otherwise return a secure Hash of input pii
+     */
+    public static String pii(boolean enablePiiLogging, Object pii) {
+        String val = String.valueOf(pii);
+        if (pii == null || TextUtils.isEmpty(val) || enablePiiLogging) {
+            return val;
+        }
+        return "[" + secureHash(val.getBytes()) + "]";
+    }
+
+    /**
+     * Returns a secure hash (using the SHA1 algorithm) of the provided input.
+     *
+     * @return "****" if the build type is user, otherwise the hash
+     * @param input the bytes for which the secure hash should be computed.
+     */
+    private static String secureHash(byte[] input) {
+        // Refrain from logging user personal information in user build.
+        if (USER_BUILD) {
+            return "****";
+        }
+
+        MessageDigest messageDigest;
+
+        try {
+            messageDigest = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException e) {
+            return "####";
+        }
+
+        byte[] result = messageDigest.digest(input);
+        return Base64.encodeToString(
+                result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
+    }
+}
diff --git a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
index 653282d..1361df3 100644
--- a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
+++ b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
@@ -320,8 +320,9 @@
                             UserHandle.USER_CURRENT);
                 }
 
-                mAtm.startActivityAndWait(null, null, mLaunchIntent, mimeType,
-                        null, null, 0, mLaunchIntent.getFlags(), null, null,
+                mAtm.startActivityAndWait(null,
+                        getInstrumentation().getContext().getBasePackageName(), mLaunchIntent,
+                        mimeType, null, null, 0, mLaunchIntent.getFlags(), null, null,
                         UserHandle.USER_CURRENT_OR_SELF);
             } catch (RemoteException e) {
                 Log.w(TAG, "Error launching app", e);
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index b4cafe4..656628eb 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -896,37 +896,76 @@
         assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
     }
 
-    /** Test that observers execute correctly for different failure reasons */
+    /** Test that observers execute correctly for failures reasons that go through thresholding. */
     @Test
-    public void testFailureReasons() {
+    public void testNonImmediateFailureReasons() {
         PackageWatchdog watchdog = createWatchdog();
         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
-        TestObserver observer3 = new TestObserver(OBSERVER_NAME_3);
-        TestObserver observer4 = new TestObserver(OBSERVER_NAME_4);
 
         watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
         watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
-        watchdog.startObservingHealth(observer3, Arrays.asList(APP_C), SHORT_DURATION);
-        watchdog.startObservingHealth(observer4, Arrays.asList(APP_D), SHORT_DURATION);
+
+        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
+                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B,
+                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
+
+        assertThat(observer1.getLastFailureReason()).isEqualTo(
+                PackageWatchdog.FAILURE_REASON_APP_CRASH);
+        assertThat(observer2.getLastFailureReason()).isEqualTo(
+                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
+    }
+
+    /** Test that observers execute correctly for failures reasons that skip thresholding. */
+    @Test
+    public void testImmediateFailures() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+
+        watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
 
         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B,
                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
-        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_C,
-                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH);
-        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_D,
-                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
 
-        assertThat(observer1.getLastFailureReason()).isEqualTo(
-                PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
-        assertThat(observer2.getLastFailureReason()).isEqualTo(
-                PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
-        assertThat(observer3.getLastFailureReason()).isEqualTo(
-                PackageWatchdog.FAILURE_REASON_APP_CRASH);
-        assertThat(observer4.getLastFailureReason()).isEqualTo(
-                PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
+        assertThat(observer1.mMitigatedPackages).containsExactly(APP_A, APP_B);
+    }
+
+    /**
+     * Test that a persistent observer will mitigate failures if it wishes to observe a package.
+     */
+    @Test
+    public void testPersistentObserverWatchesPackage() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1);
+        persistentObserver.setPersistent(true);
+        persistentObserver.setMayObservePackages(true);
+
+        watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+
+        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
+                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
+        assertThat(persistentObserver.mHealthCheckFailedPackages).containsExactly(APP_A);
+    }
+
+    /**
+     * Test that a persistent observer will not mitigate failures if it does not wish to observe
+     * a given package.
+     */
+    @Test
+    public void testPersistentObserverDoesNotWatchPackage() {
+        PackageWatchdog watchdog = createWatchdog();
+        TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1);
+        persistentObserver.setPersistent(true);
+        persistentObserver.setMayObservePackages(false);
+
+        watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+
+        raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
+                VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
+        assertThat(persistentObserver.mHealthCheckFailedPackages).isEmpty();
     }
 
     private void adoptShellPermissions(String... permissions) {
@@ -964,7 +1003,12 @@
     /** Trigger package failures above the threshold. */
     private void raiseFatalFailureAndDispatch(PackageWatchdog watchdog,
             List<VersionedPackage> packages, int failureReason) {
-        for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
+        long triggerFailureCount = watchdog.getTriggerFailureCount();
+        if (failureReason == PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK
+                || failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+            triggerFailureCount = 1;
+        }
+        for (int i = 0; i < triggerFailureCount; i++) {
             watchdog.onPackageFailure(packages, failureReason);
         }
         mTestLooper.dispatchAll();
@@ -1000,6 +1044,8 @@
         private final String mName;
         private int mImpact;
         private int mLastFailureReason;
+        private boolean mIsPersistent = false;
+        private boolean mMayObservePackages = false;
         final List<String> mHealthCheckFailedPackages = new ArrayList<>();
         final List<String> mMitigatedPackages = new ArrayList<>();
 
@@ -1028,9 +1074,25 @@
             return mName;
         }
 
+        public boolean isPersistent() {
+            return mIsPersistent;
+        }
+
+        public boolean mayObservePackage(String packageName) {
+            return mMayObservePackages;
+        }
+
         public int getLastFailureReason() {
             return mLastFailureReason;
         }
+
+        public void setPersistent(boolean persistent) {
+            mIsPersistent = persistent;
+        }
+
+        public void setMayObservePackages(boolean mayObservePackages) {
+            mMayObservePackages = mayObservePackages;
+        }
     }
 
     private static class TestController extends ExplicitHealthCheckController {
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 2bc129a..091edd4 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -19,15 +19,17 @@
     static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"],
     test_suites: ["general-tests"],
     test_config: "RollbackTest.xml",
+    java_resources: [":com.android.apex.apkrollback.test_v2"],
 }
 
 java_test_host {
     name: "StagedRollbackTest",
     srcs: ["StagedRollbackTest/src/**/*.java"],
     libs: ["tradefed"],
-    static_libs: ["testng"],
+    static_libs: ["testng", "compatibility-tradefed"],
     test_suites: ["general-tests"],
     test_config: "StagedRollbackTest.xml",
+    data: [":com.android.apex.apkrollback.test_v1"],
 }
 
 java_test_host {
@@ -37,3 +39,44 @@
     test_suites: ["general-tests"],
     test_config: "MultiUserRollbackTest.xml",
 }
+
+genrule {
+  name: "com.android.apex.apkrollback.test.pem",
+  out: ["com.android.apex.apkrollback.test.pem"],
+  cmd: "openssl genrsa -out $(out) 4096",
+}
+
+genrule {
+  name: "com.android.apex.apkrollback.test.pubkey",
+  srcs: [":com.android.apex.apkrollback.test.pem"],
+  out: ["com.android.apex.apkrollback.test.pubkey"],
+  tools: ["avbtool"],
+  cmd: "$(location avbtool) extract_public_key --key $(in) --output $(out)",
+}
+
+apex_key {
+  name: "com.android.apex.apkrollback.test.key",
+  private_key: ":com.android.apex.apkrollback.test.pem",
+  public_key: ":com.android.apex.apkrollback.test.pubkey",
+  installable: false,
+}
+
+apex {
+  name: "com.android.apex.apkrollback.test_v1",
+  manifest: "testdata/manifest_v1.json",
+  androidManifest: "testdata/AndroidManifest.xml",
+  file_contexts: ":apex.test-file_contexts",
+  key: "com.android.apex.apkrollback.test.key",
+  apps: ["TestAppAv1"],
+  installable: false,
+}
+
+apex {
+  name: "com.android.apex.apkrollback.test_v2",
+  manifest: "testdata/manifest_v2.json",
+  androidManifest: "testdata/AndroidManifest.xml",
+  file_contexts: ":apex.test-file_contexts",
+  key: "com.android.apex.apkrollback.test.key",
+  apps: ["TestAppAv2"],
+  installable: false,
+}
\ No newline at end of file
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 40169b8..3877cc1 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -324,6 +324,7 @@
 
     @Test
     public void testNetworkPassedDoesNotRollback_Phase1() throws Exception {
+        // Remove available rollbacks and uninstall NetworkStack on /data/
         RollbackManager rm = RollbackUtils.getRollbackManager();
         String networkStack = getNetworkStackPackageName();
 
@@ -332,6 +333,15 @@
 
         assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
                         networkStack)).isNull();
+
+        // Reduce health check deadline, here unlike the network failed case, we use
+        // a longer deadline because joining a network can take a much longer time for
+        // reasons external to the device than 'not joining'
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS,
+                Integer.toString(300000), false);
+        // Simulate re-installation of new NetworkStack with rollbacks enabled
+        installNetworkStackPackage();
     }
 
     @Test
@@ -343,6 +353,9 @@
 
     @Test
     public void testNetworkPassedDoesNotRollback_Phase3() throws Exception {
+        // Sleep for > health check deadline. We expect no rollback should happen during sleeping.
+        // If the device reboots for rollback, this device test will fail as well as the host test.
+        Thread.sleep(TimeUnit.SECONDS.toMillis(310));
         RollbackManager rm = RollbackUtils.getRollbackManager();
         assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
                         getNetworkStackPackageName())).isNull();
@@ -380,11 +393,6 @@
     }
 
     @Test
-    public void testRollbackWhitelistedApp_cleanUp() throws Exception {
-        RollbackUtils.getRollbackManager().expireRollbackForPackage(getModuleMetadataPackageName());
-    }
-
-    @Test
     public void testRollbackDataPolicy_Phase1() throws Exception {
         Uninstall.packages(TestApp.A, TestApp.B);
         Install.multi(TestApp.A1, TestApp.B1).commit();
@@ -422,6 +430,69 @@
         assertThat(InstallUtils.getUserDataVersion(TestApp.B)).isEqualTo(1);
     }
 
+    @Test
+    public void testCleanUp() throws Exception {
+        // testNativeWatchdogTriggersRollback will fail if multiple staged sessions are
+        // committed on a device which doesn't support checkpoint. Let's clean up all rollbacks
+        // so there is only one rollback to commit when testing native crashes.
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+        rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream())
+                .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage);
+    }
+
+    private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
+    private static final TestApp TEST_APEX_WITH_APK_V1 = new TestApp("TestApexWithApkV1",
+            APK_IN_APEX_TESTAPEX_NAME, 1, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v1.apex");
+    private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2",
+            APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex");
+    private static final TestApp TEST_APP_A_V2_UNKNOWN = new TestApp("Av2Unknown", TestApp.A, 0,
+            /*isApex*/false, "TestAppAv2.apk");
+
+    @Test
+    public void testRollbackApexWithApk_Phase1() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
+
+        int sessionId = Install.single(TEST_APEX_WITH_APK_V2).setStaged().setEnableRollback()
+                .commit();
+        InstallUtils.waitForSessionReady(sessionId);
+    }
+
+    @Test
+    public void testRollbackApexWithApk_Phase2() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        InstallUtils.processUserData(TestApp.A);
+
+        RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME);
+        assertThat(available).isStaged();
+        assertThat(available).packagesContainsExactly(
+                Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1),
+                Rollback.from(TEST_APP_A_V2_UNKNOWN).to(TestApp.A1));
+
+        RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2);
+        RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId());
+        assertThat(committed).isNotNull();
+        assertThat(committed).isStaged();
+        assertThat(committed).packagesContainsExactly(
+                Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1),
+                Rollback.from(TEST_APP_A_V2_UNKNOWN).to(TestApp.A1));
+        assertThat(committed).causePackagesContainsExactly(TEST_APEX_WITH_APK_V2);
+        assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+
+        // Note: The app is not rolled back until after the rollback is staged
+        // and the device has been rebooted.
+        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+        assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2);
+    }
+
+    @Test
+    public void testRollbackApexWithApk_Phase3() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
+    }
+
     private static void runShellCommand(String cmd) {
         ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .executeShellCommand(cmd);
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 4644d8a..6daa6bc 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -19,14 +19,16 @@
 import static org.junit.Assert.assertTrue;
 import static org.testng.Assert.assertThrows;
 
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
+import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.File;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -48,8 +50,31 @@
                     phase));
     }
 
+    private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
+
     @Before
     public void setUp() throws Exception {
+        if (!getDevice().isAdbRoot()) {
+            getDevice().enableAdbRoot();
+        }
+        getDevice().remountSystemWritable();
+        getDevice().executeShellCommand(
+                "rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex "
+                        + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
+        getDevice().reboot();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        runPhase("testCleanUp");
+
+        if (!getDevice().isAdbRoot()) {
+            getDevice().enableAdbRoot();
+        }
+        getDevice().remountSystemWritable();
+        getDevice().executeShellCommand(
+                "rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex "
+                        + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
         getDevice().reboot();
     }
 
@@ -128,21 +153,8 @@
      * Tests passed network health check does not trigger watchdog staged rollbacks.
      */
     @Test
-    @Ignore("b/143514090")
     public void testNetworkPassedDoesNotRollback() throws Exception {
-        // Remove available rollbacks and uninstall NetworkStack on /data/
         runPhase("testNetworkPassedDoesNotRollback_Phase1");
-        // Reduce health check deadline, here unlike the network failed case, we use
-        // a longer deadline because joining a network can take a much longer time for
-        // reasons external to the device than 'not joining'
-        getDevice().executeShellCommand("device_config put rollback "
-                + "watchdog_request_timeout_millis 300000");
-        // Simulate re-installation of new NetworkStack with rollbacks enabled
-        getDevice().executeShellCommand("pm install -r --staged --enable-rollback "
-                + getNetworkStackPath());
-
-        // Sleep to allow writes to disk before reboot
-        Thread.sleep(5000);
         // Reboot device to activate staged package
         getDevice().reboot();
 
@@ -157,8 +169,6 @@
         // on mobile data
         getDevice().waitForDeviceAvailable();
 
-        // Sleep for > health check deadline
-        Thread.sleep(310000);
         // Verify rollback was not executed after health check deadline
         runPhase("testNetworkPassedDoesNotRollback_Phase3");
     }
@@ -180,16 +190,9 @@
      */
     @Test
     public void testRollbackWhitelistedApp() throws Exception {
-        try {
-            runPhase("testRollbackWhitelistedApp_Phase1");
-            getDevice().reboot();
-            runPhase("testRollbackWhitelistedApp_Phase2");
-        } finally {
-            // testNativeWatchdogTriggersRollback will fail if multiple staged sessions are
-            // committed on a device which doesn't support checkpoint. Let's clean up the rollback
-            // so there is only one rollback to commit when testing native crashes.
-            runPhase("testRollbackWhitelistedApp_cleanUp");
-        }
+        runPhase("testRollbackWhitelistedApp_Phase1");
+        getDevice().reboot();
+        runPhase("testRollbackWhitelistedApp_Phase2");
     }
 
     @Test
@@ -201,6 +204,28 @@
         runPhase("testRollbackDataPolicy_Phase3");
     }
 
+    /**
+     * Tests that userdata of apk-in-apex is restored when apex is rolled back.
+     */
+    @Test
+    public void testRollbackApexWithApk() throws Exception {
+        getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A");
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
+        final File apex = buildHelper.getTestFile(fileName);
+        if (!getDevice().isAdbRoot()) {
+            getDevice().enableAdbRoot();
+        }
+        getDevice().remountSystemWritable();
+        assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName));
+        getDevice().reboot();
+        runPhase("testRollbackApexWithApk_Phase1");
+        getDevice().reboot();
+        runPhase("testRollbackApexWithApk_Phase2");
+        getDevice().reboot();
+        runPhase("testRollbackApexWithApk_Phase3");
+    }
+
     private void crashProcess(String processName, int numberOfCrashes) throws Exception {
         String pid = "";
         String lastPid = "invalid";
diff --git a/tests/RollbackTest/testdata/AndroidManifest.xml b/tests/RollbackTest/testdata/AndroidManifest.xml
new file mode 100644
index 0000000..f21ec89
--- /dev/null
+++ b/tests/RollbackTest/testdata/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.apex.apkrollback.test">
+    <!-- APEX does not have classes.dex -->
+    <application android:hasCode="false" />
+    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="29"/>
+</manifest>
+
diff --git a/tests/RollbackTest/testdata/manifest_v1.json b/tests/RollbackTest/testdata/manifest_v1.json
new file mode 100644
index 0000000..1762fc6
--- /dev/null
+++ b/tests/RollbackTest/testdata/manifest_v1.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.apex.apkrollback.test",
+  "version": 1
+}
diff --git a/tests/RollbackTest/testdata/manifest_v2.json b/tests/RollbackTest/testdata/manifest_v2.json
new file mode 100644
index 0000000..c5127b9c
--- /dev/null
+++ b/tests/RollbackTest/testdata/manifest_v2.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.apex.apkrollback.test",
+  "version": 2
+}
diff --git a/tests/TaskOrganizerTest/Android.bp b/tests/TaskOrganizerTest/Android.bp
new file mode 100644
index 0000000..8a13dbc
--- /dev/null
+++ b/tests/TaskOrganizerTest/Android.bp
@@ -0,0 +1,22 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test {
+    name: "TaskOrganizerTest",
+    srcs: ["**/*.java"],
+    platform_apis: true,
+    certificate: "platform",
+}
diff --git a/tests/TaskOrganizerTest/AndroidManifest.xml b/tests/TaskOrganizerTest/AndroidManifest.xml
new file mode 100644
index 0000000..0cb6c10
--- /dev/null
+++ b/tests/TaskOrganizerTest/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+          http://www.apache.org/licenses/LICENSE-2.0
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.test.taskembed">
+    <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <application>
+      <service android:name=".TaskOrganizerPipTest"
+               android:exported="true">
+      </service>
+    </application>
+</manifest>
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
new file mode 100644
index 0000000..6ffa19d
--- /dev/null
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.taskembed;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.Service;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.ITaskOrganizer;
+import android.view.IWindowContainer;
+import android.view.WindowContainerTransaction;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+public class TaskOrganizerPipTest extends Service {
+    static final int PIP_WIDTH  = 640;
+    static final int PIP_HEIGHT = 360;
+
+    class PipOrgView extends SurfaceView implements SurfaceHolder.Callback {
+        PipOrgView(Context c) {
+            super(c);
+            getHolder().addCallback(this);
+            setZOrderOnTop(true);
+        }
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            try {
+                ActivityTaskManager.getService().registerTaskOrganizer(mOrganizer,
+                        WindowConfiguration.WINDOWING_MODE_PINNED);
+            } catch (Exception e) {
+            }
+        }
+
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+        }
+
+        void reparentTask(IWindowContainer wc) {
+            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            SurfaceControl leash = null;
+            try {
+                leash = wc.getLeash();
+            } catch (Exception e) {
+                // System server died.. oh well
+            }
+            t.reparent(leash, getSurfaceControl())
+                .setPosition(leash, 0, 0)
+                .apply();
+        }
+    }
+
+    PipOrgView mPipView;
+
+    class Organizer extends ITaskOrganizer.Stub {
+        public void taskAppeared(IWindowContainer wc, ActivityManager.RunningTaskInfo ti) {
+            mPipView.reparentTask(wc);
+
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            wct.scheduleFinishEnterPip(wc, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT));
+            try {
+                ActivityTaskManager.getService().applyContainerTransaction(wct);
+            } catch (Exception e) {
+            }
+        }
+        public void taskVanished(IWindowContainer wc) {
+        }
+        public void transactionReady(int id, SurfaceControl.Transaction t) {
+        }
+    }
+
+    Organizer mOrganizer = new Organizer();
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        final WindowManager.LayoutParams wlp = new WindowManager.LayoutParams();
+        wlp.setTitle("TaskOrganizerPipTest");
+        wlp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        wlp.width = wlp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+
+        FrameLayout layout = new FrameLayout(this);
+        ViewGroup.LayoutParams lp =
+            new ViewGroup.LayoutParams(PIP_WIDTH, PIP_HEIGHT);
+        mPipView = new PipOrgView(this);
+        layout.addView(mPipView, lp);
+
+        WindowManager wm = getSystemService(WindowManager.class);
+        wm.addView(layout, wlp);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+}
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
index 1e8d83c..0ab5c97 100644
--- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -35,10 +35,10 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
 import android.net.NetworkInfo;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
 import android.net.NetworkSpecifier;
 import android.net.SocketKeepalive;
 import android.net.UidRange;
@@ -114,7 +114,7 @@
         public InstrumentedNetworkAgent(NetworkAgentWrapper wrapper, LinkProperties lp) {
             super(wrapper.mHandlerThread.getLooper(), wrapper.mContext, wrapper.mLogTag,
                     wrapper.mNetworkInfo, wrapper.mNetworkCapabilities, lp, wrapper.mScore,
-                    new NetworkMisc(), NetworkFactory.SerialNumber.NONE);
+                    new NetworkAgentConfig(), NetworkProvider.ID_NONE);
             mWrapper = wrapper;
         }
 
diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java
index c16a0f4..33d77d2 100644
--- a/tests/net/java/android/net/NetworkStatsTest.java
+++ b/tests/net/java/android/net/NetworkStatsTest.java
@@ -64,15 +64,15 @@
     @Test
     public void testFindIndex() throws Exception {
         final NetworkStats stats = new NetworkStats(TEST_START, 5)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 1024L, 8L, 0L, 0L, 10)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 0L, 0L, 1024L, 8L, 11)
-                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 1024L, 8L, 1024L, 8L, 12)
-                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
+                .addEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
                         DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12);
 
         assertEquals(4, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES,
@@ -94,21 +94,21 @@
     @Test
     public void testFindIndexHinted() {
         final NetworkStats stats = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 1024L, 8L, 0L, 0L, 10)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11)
-                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 1024L, 8L, 0L, 0L, 10)
-                .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 0L, 0L, 1024L, 8L, 11)
-                .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11)
-                .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12)
-                .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
+                .addEntry(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
                         DEFAULT_NETWORK_NO, 1024L, 8L, 1024L, 8L, 12);
 
         // verify that we correctly find across regardless of hinting
@@ -143,27 +143,27 @@
         assertEquals(0, stats.size());
         assertEquals(4, stats.internalSize());
 
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 DEFAULT_NETWORK_YES, 1L, 1L, 2L, 2L, 3);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 DEFAULT_NETWORK_NO, 2L, 2L, 2L, 2L, 4);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
                 DEFAULT_NETWORK_YES, 3L, 3L, 2L, 2L, 5);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
                 DEFAULT_NETWORK_NO, 3L, 3L, 2L, 2L, 5);
 
         assertEquals(4, stats.size());
         assertEquals(4, stats.internalSize());
 
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 DEFAULT_NETWORK_NO, 4L, 40L, 4L, 40L, 7);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 DEFAULT_NETWORK_YES, 5L, 50L, 4L, 40L, 8);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 DEFAULT_NETWORK_NO, 6L, 60L, 5L, 50L, 10);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
                 DEFAULT_NETWORK_YES, 7L, 70L, 5L, 50L, 11);
-        stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
+        stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
                 DEFAULT_NETWORK_NO, 7L, 70L, 5L, 50L, 11);
 
         assertEquals(9, stats.size());
@@ -193,8 +193,8 @@
     public void testCombineExisting() throws Exception {
         final NetworkStats stats = new NetworkStats(TEST_START, 10);
 
-        stats.addValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 10);
-        stats.addValues(TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2);
+        stats.addEntry(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 10);
+        stats.addEntry(TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2);
         stats.combineValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, -128L, -1L,
                 -128L, -1L, -1);
 
@@ -215,12 +215,12 @@
     @Test
     public void testSubtractIdenticalData() throws Exception {
         final NetworkStats before = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
 
         final NetworkStats after = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
 
         final NetworkStats result = after.subtract(before);
 
@@ -234,12 +234,12 @@
     @Test
     public void testSubtractIdenticalRows() throws Exception {
         final NetworkStats before = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
 
         final NetworkStats after = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1025L, 9L, 2L, 1L, 15)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 3L, 1L, 1028L, 9L, 20);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1025L, 9L, 2L, 1L, 15)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 3L, 1L, 1028L, 9L, 20);
 
         final NetworkStats result = after.subtract(before);
 
@@ -253,13 +253,13 @@
     @Test
     public void testSubtractNewRows() throws Exception {
         final NetworkStats before = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
 
         final NetworkStats after = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12)
-                .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 20);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12)
+                .addEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 20);
 
         final NetworkStats result = after.subtract(before);
 
@@ -275,11 +275,11 @@
     @Test
     public void testSubtractMissingRows() throws Exception {
         final NetworkStats before = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 0L, 0L, 0L, 0)
-                .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2048L, 0L, 0L, 0L, 0);
+                .addEntry(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 0L, 0L, 0L, 0)
+                .addEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2048L, 0L, 0L, 0L, 0);
 
         final NetworkStats after = new NetworkStats(TEST_START, 1)
-                .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2049L, 2L, 3L, 4L, 0);
+                .addEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2049L, 2L, 3L, 4L, 0);
 
         final NetworkStats result = after.subtract(before);
 
@@ -293,40 +293,40 @@
     @Test
     public void testTotalBytes() throws Exception {
         final NetworkStats iface = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 128L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 256L, 0L, 0L, 0L, 0L);
+                .addEntry(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 128L, 0L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 256L, 0L, 0L, 0L, 0L);
         assertEquals(384L, iface.getTotalBytes());
 
         final NetworkStats uidSet = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE, 101, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
         assertEquals(96L, uidSet.getTotalBytes());
 
         final NetworkStats uidTag = new NetworkStats(TEST_START, 6)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L);
         assertEquals(64L, uidTag.getTotalBytes());
 
         final NetworkStats uidMetered = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L);
         assertEquals(96L, uidMetered.getTotalBytes());
 
         final NetworkStats uidRoaming = new NetworkStats(TEST_START, 3)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
                         DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L);
         assertEquals(96L, uidRoaming.getTotalBytes());
     }
@@ -343,11 +343,11 @@
     @Test
     public void testGroupedByIfaceAll() throws Exception {
         final NetworkStats uidStats = new NetworkStats(TEST_START, 3)
-                .addValues(IFACE_ALL, 100, SET_ALL, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(IFACE_ALL, 100, SET_ALL, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L)
-                .addValues(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_NO,
+                .addEntry(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 128L, 8L, 0L, 2L, 20L)
-                .addValues(IFACE_ALL, 101, SET_ALL, TAG_NONE, METERED_NO, ROAMING_YES,
+                .addEntry(IFACE_ALL, 101, SET_ALL, TAG_NONE, METERED_NO, ROAMING_YES,
                         DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L);
         final NetworkStats grouped = uidStats.groupedByIface();
 
@@ -361,19 +361,19 @@
     @Test
     public void testGroupedByIface() throws Exception {
         final NetworkStats uidStats = new NetworkStats(TEST_START, 7)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 64L, 4L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
                         DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L);
 
         final NetworkStats grouped = uidStats.groupedByIface();
@@ -390,19 +390,19 @@
     @Test
     public void testAddAllValues() {
         final NetworkStats first = new NetworkStats(TEST_START, 5)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES,
+                .addEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES,
                         DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L);
 
         final NetworkStats second = new NetworkStats(TEST_START, 2)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES,
+                .addEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES,
                         DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L);
 
         first.combineAllValues(second);
@@ -421,19 +421,19 @@
     @Test
     public void testGetTotal() {
         final NetworkStats stats = new NetworkStats(TEST_START, 7)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 64L, 4L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 512L,32L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
                         DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L);
 
         assertValues(stats.getTotal(null), 1408L, 88L, 0L, 2L, 20L);
@@ -459,7 +459,7 @@
         assertEquals(0, after.size());
 
         // Test 1 item stats.
-        before.addValues(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, 1L, 128L, 0L, 2L, 20L);
+        before.addEntry(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, 1L, 128L, 0L, 2L, 20L);
         after = before.clone();
         after.removeUids(new int[0]);
         assertEquals(1, after.size());
@@ -469,12 +469,12 @@
         assertEquals(0, after.size());
 
         // Append remaining test items.
-        before.addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 16L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 8L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 4L, 0L, 0L, 0L)
-                .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 64L, 2L, 0L, 0L, 0L);
+        before.addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L)
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 16L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 8L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 4L, 0L, 0L, 0L)
+                .addEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 64L, 2L, 0L, 0L, 0L);
         assertEquals(7, before.size());
 
         // Test remove with empty uid list.
@@ -505,12 +505,12 @@
     @Test
     public void testClone() throws Exception {
         final NetworkStats original = new NetworkStats(TEST_START, 5)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L);
 
         // make clone and mutate original
         final NetworkStats clone = original.clone();
-        original.addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L);
+        original.addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L);
 
         assertEquals(3, original.size());
         assertEquals(2, clone.size());
@@ -523,8 +523,8 @@
     public void testAddWhenEmpty() throws Exception {
         final NetworkStats red = new NetworkStats(TEST_START, -1);
         final NetworkStats blue = new NetworkStats(TEST_START, 5)
-                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
-                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L);
+                .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+                .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L);
 
         // We're mostly checking that we don't crash
         red.combineAllValues(blue);
@@ -537,39 +537,39 @@
         final String underlyingIface = "wlan0";
         final int testTag1 = 8888;
         NetworkStats delta = new NetworkStats(TEST_START, 17)
-            .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 39605L, 46L, 12259L, 55L, 0L)
-            .addValues(tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
-            .addValues(tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 72667L, 197L, 43909L, 241L, 0L)
-            .addValues(tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 9297L, 17L, 4128L, 21L, 0L)
-            // VPN package also uses some traffic through unprotected network.
-            .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 4983L, 10L, 1801L, 12L, 0L)
-            .addValues(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
-            // Tag entries
-            .addValues(tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 21691L, 41L, 13820L, 51L, 0L)
-            .addValues(tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 1281L, 2L, 665L, 2L, 0L)
-            // Irrelevant entries
-            .addValues(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 1685L, 5L, 2070L, 6L, 0L)
-            // Underlying Iface entries
-            .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 5178L, 8L, 2139L, 11L, 0L)
-            .addValues(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
-            .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 149873L, 287L, 59217L /* smaller than sum(tun0) */,
-                    299L /* smaller than sum(tun0) */, 0L)
-            .addValues(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
+                .addEntry(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 39605L, 46L, 12259L, 55L, 0L)
+                .addEntry(tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
+                .addEntry(tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 72667L, 197L, 43909L, 241L, 0L)
+                .addEntry(tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 9297L, 17L, 4128L, 21L, 0L)
+                // VPN package also uses some traffic through unprotected network.
+                .addEntry(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 4983L, 10L, 1801L, 12L, 0L)
+                .addEntry(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
+                // Tag entries
+                .addEntry(tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 21691L, 41L, 13820L, 51L, 0L)
+                .addEntry(tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 1281L, 2L, 665L, 2L, 0L)
+                // Irrelevant entries
+                .addEntry(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 1685L, 5L, 2070L, 6L, 0L)
+                // Underlying Iface entries
+                .addEntry(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 5178L, 8L, 2139L, 11L, 0L)
+                .addEntry(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
+                .addEntry(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 149873L, 287L, 59217L /* smaller than sum(tun0) */,
+                        299L /* smaller than sum(tun0) */, 0L)
+                .addEntry(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
 
-        delta.migrateTun(tunUid, tunIface, new String[] {underlyingIface});
+        delta.migrateTun(tunUid, tunIface, new String[]{underlyingIface});
         assertEquals(20, delta.size());
 
         // tunIface and TEST_IFACE entries are not changed.
@@ -634,21 +634,21 @@
         final String tunIface = "tun0";
         final String underlyingIface = "wlan0";
         NetworkStats delta = new NetworkStats(TEST_START, 9)
-            // 2 different apps sent/receive data via tun0.
-            .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L)
-            .addValues(tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 500L, 2L, 200L, 5L, 0L)
-            // VPN package resends data through the tunnel (with exaggerated overhead)
-            .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 240000, 100L, 120000L, 60L, 0L)
-            // 1 app already has some traffic on the underlying interface, the other doesn't yet
-            .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO, 1000L, 10L, 2000L, 20L, 0L)
-            // Traffic through the underlying interface via the vpn app.
-            // This test should redistribute this data correctly.
-            .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
-                    DEFAULT_NETWORK_NO,  75500L, 37L, 130000L, 70L, 0L);
+                // 2 different apps sent/receive data via tun0.
+                .addEntry(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L)
+                .addEntry(tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 500L, 2L, 200L, 5L, 0L)
+                // VPN package resends data through the tunnel (with exaggerated overhead)
+                .addEntry(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 240000, 100L, 120000L, 60L, 0L)
+                // 1 app already has some traffic on the underlying interface, the other doesn't yet
+                .addEntry(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 1000L, 10L, 2000L, 20L, 0L)
+                // Traffic through the underlying interface via the vpn app.
+                // This test should redistribute this data correctly.
+                .addEntry(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                        DEFAULT_NETWORK_NO, 75500L, 37L, 130000L, 70L, 0L);
 
         delta.migrateTun(tunUid, tunIface, new String[]{underlyingIface});
         assertEquals(9, delta.size());
@@ -697,9 +697,9 @@
                 DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
 
         NetworkStats stats = new NetworkStats(TEST_START, 3)
-                .addValues(entry1)
-                .addValues(entry2)
-                .addValues(entry3);
+                .addEntry(entry1)
+                .addEntry(entry2)
+                .addEntry(entry3);
 
         stats.filter(UID_ALL, INTERFACES_ALL, TAG_ALL);
         assertEquals(3, stats.size());
@@ -724,9 +724,9 @@
                 DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
 
         NetworkStats stats = new NetworkStats(TEST_START, 3)
-                .addValues(entry1)
-                .addValues(entry2)
-                .addValues(entry3);
+                .addEntry(entry1)
+                .addEntry(entry2)
+                .addEntry(entry3);
 
         stats.filter(testUid, INTERFACES_ALL, TAG_ALL);
         assertEquals(2, stats.size());
@@ -755,10 +755,10 @@
                 DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
 
         NetworkStats stats = new NetworkStats(TEST_START, 4)
-                .addValues(entry1)
-                .addValues(entry2)
-                .addValues(entry3)
-                .addValues(entry4);
+                .addEntry(entry1)
+                .addEntry(entry2)
+                .addEntry(entry3)
+                .addEntry(entry4);
 
         stats.filter(UID_ALL, new String[] { testIf1, testIf2 }, TAG_ALL);
         assertEquals(3, stats.size());
@@ -778,8 +778,8 @@
                 DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
 
         NetworkStats stats = new NetworkStats(TEST_START, 3)
-                .addValues(entry1)
-                .addValues(entry2);
+                .addEntry(entry1)
+                .addEntry(entry2);
 
         stats.filter(UID_ALL, new String[] { }, TAG_ALL);
         assertEquals(0, stats.size());
@@ -802,9 +802,9 @@
                 DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
 
         NetworkStats stats = new NetworkStats(TEST_START, 3)
-                .addValues(entry1)
-                .addValues(entry2)
-                .addValues(entry3);
+                .addEntry(entry1)
+                .addEntry(entry2)
+                .addEntry(entry3);
 
         stats.filter(UID_ALL, INTERFACES_ALL, testTag);
         assertEquals(2, stats.size());
@@ -831,10 +831,10 @@
                 DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
 
         NetworkStats stats = new NetworkStats(TEST_START, 4)
-                .addValues(entry1)
-                .addValues(entry2)
-                .addValues(entry3)
-                .addValues(entry4);
+                .addEntry(entry1)
+                .addEntry(entry2)
+                .addEntry(entry3)
+                .addEntry(entry4);
 
         stats.filterDebugEntries();
 
@@ -891,14 +891,14 @@
                 0 /* operations */);
 
         final NetworkStats statsXt = new NetworkStats(TEST_START, 3)
-                .addValues(appEntry)
-                .addValues(xtRootUidEntry)
-                .addValues(otherEntry);
+                .addEntry(appEntry)
+                .addEntry(xtRootUidEntry)
+                .addEntry(otherEntry);
 
         final NetworkStats statsEbpf = new NetworkStats(TEST_START, 3)
-                .addValues(appEntry)
-                .addValues(ebpfRootUidEntry)
-                .addValues(otherEntry);
+                .addEntry(appEntry)
+                .addEntry(ebpfRootUidEntry)
+                .addEntry(otherEntry);
 
         statsXt.apply464xlatAdjustments(stackedIface, false);
         statsEbpf.apply464xlatAdjustments(stackedIface, true);
@@ -945,8 +945,8 @@
                 0 /* operations */);
 
         NetworkStats stats = new NetworkStats(TEST_START, 2)
-                .addValues(firstEntry)
-                .addValues(secondEntry);
+                .addEntry(firstEntry)
+                .addEntry(secondEntry);
 
         // Empty map: no adjustment
         stats.apply464xlatAdjustments(new ArrayMap<>(), false);
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 535298f..9e915ae 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -35,10 +35,10 @@
 import android.net.IDnsResolver;
 import android.net.INetd;
 import android.net.Network;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
 import android.net.NetworkInfo;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
 import android.net.NetworkScore;
 import android.os.INetworkManagementService;
 import android.text.format.DateUtils;
@@ -75,7 +75,7 @@
     @Mock INetd mNetd;
     @Mock INetworkManagementService mNMS;
     @Mock Context mCtx;
-    @Mock NetworkMisc mMisc;
+    @Mock NetworkAgentConfig mAgentConfig;
     @Mock NetworkNotificationManager mNotifier;
     @Mock Resources mResources;
 
@@ -358,8 +358,8 @@
         NetworkScore ns = new NetworkScore();
         ns.putIntExtension(NetworkScore.LEGACY_SCORE, 50);
         NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null,
-                caps, ns, mCtx, null, mMisc, mConnService, mNetd, mDnsResolver, mNMS,
-                NetworkFactory.SerialNumber.NONE);
+                caps, ns, mCtx, null, mAgentConfig, mConnService, mNetd, mDnsResolver, mNMS,
+                NetworkProvider.ID_NONE);
         nai.everValidated = true;
         return nai;
     }
diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
index b709af1..cf70f5d 100644
--- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
+++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
@@ -33,8 +33,8 @@
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkInfo;
-import android.net.NetworkMisc;
 import android.os.Handler;
 import android.os.INetworkManagementService;
 import android.os.test.TestLooper;
@@ -63,7 +63,7 @@
     static final int NETID = 42;
 
     @Mock ConnectivityService mConnectivity;
-    @Mock NetworkMisc mMisc;
+    @Mock NetworkAgentConfig mAgentConfig;
     @Mock IDnsResolver mDnsResolver;
     @Mock INetd mNetd;
     @Mock INetworkManagementService mNms;
@@ -93,7 +93,7 @@
         mNai.networkInfo = new NetworkInfo(null);
         mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI);
         when(mNai.connService()).thenReturn(mConnectivity);
-        when(mNai.netMisc()).thenReturn(mMisc);
+        when(mNai.netAgentConfig()).thenReturn(mAgentConfig);
         when(mNai.handler()).thenReturn(mHandler);
 
         when(mNms.getInterfaceConfig(eq(STACKED_IFACE))).thenReturn(mConfig);
@@ -104,7 +104,7 @@
         String msg = String.format("requiresClat expected %b for type=%d state=%s skip=%b "
                 + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
                 nai.networkInfo.getDetailedState(),
-                mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(),
+                mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(),
                 nai.linkProperties.getLinkAddresses());
         assertEquals(msg, expected, Nat464Xlat.requiresClat(nai));
     }
@@ -113,7 +113,7 @@
         String msg = String.format("shouldStartClat expected %b for type=%d state=%s skip=%b "
                 + "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
                 nai.networkInfo.getDetailedState(),
-                mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(),
+                mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(),
                 nai.linkProperties.getLinkAddresses());
         assertEquals(msg, expected, Nat464Xlat.shouldStartClat(nai));
     }
@@ -151,11 +151,11 @@
                 assertRequiresClat(true, mNai);
                 assertShouldStartClat(true, mNai);
 
-                mMisc.skip464xlat = true;
+                mAgentConfig.skip464xlat = true;
                 assertRequiresClat(false, mNai);
                 assertShouldStartClat(false, mNai);
 
-                mMisc.skip464xlat = false;
+                mAgentConfig.skip464xlat = false;
                 assertRequiresClat(true, mNai);
                 assertShouldStartClat(true, mNai);
 
diff --git a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
index c0f9dc1..f0e5774 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -326,14 +326,14 @@
         // Baseline
         NetworkStats xtSnapshot = null;
         NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
@@ -359,14 +359,14 @@
         // Baseline
         NetworkStats xtSnapshot = null;
         NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
@@ -391,14 +391,14 @@
         // Baseline
         NetworkStats xtSnapshot = null;
         NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, BASE_BYTES + THRESHOLD_BYTES, 2L,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
@@ -424,14 +424,14 @@
         // Baseline
         NetworkStats xtSnapshot = null;
         NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
+                .addEntry(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
                         ROAMING_NO, DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
                 xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
 
         // Delta
         uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
-                .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
+                .addEntry(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
                         ROAMING_NO, DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
                         BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
         mStatsObservers.updateStats(
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 4d42a61..a9e0b9a 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -80,6 +80,7 @@
 import android.net.NetworkStats;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -102,6 +103,7 @@
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
 import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
 import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.TestableNetworkStatsProvider;
 
 import libcore.io.IoUtils;
 
@@ -298,11 +300,11 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 1024L, 8L, 2048L, 16L));
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
         mService.setUidForeground(UID_RED, false);
         mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
         mService.setUidForeground(UID_RED, true);
@@ -407,9 +409,9 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
         mService.incrementOperationCount(UID_RED, 0xF00D, 10);
 
         forcePollAndWaitForIdle();
@@ -429,9 +431,9 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
 
         mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
         forcePollAndWaitForIdle();
@@ -443,10 +445,10 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 2176L, 17L, 1536L, 12L));
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L));
         mService.incrementOperationCount(UID_BLUE, 0xFAAD, 10);
 
         forcePollAndWaitForIdle();
@@ -480,10 +482,10 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L));
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
-                .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
+                .addEntry(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
         mService.incrementOperationCount(UID_RED, 0xFAAD, 10);
 
         forcePollAndWaitForIdle();
@@ -501,10 +503,10 @@
         expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
                 .addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L));
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
-                .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
+                .addEntry(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
         final Intent intent = new Intent(ACTION_UID_REMOVED);
         intent.putExtra(EXTRA_UID, UID_BLUE);
         mServiceContext.sendBroadcast(intent);
@@ -536,8 +538,8 @@
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
         mService.incrementOperationCount(UID_RED, 0xF00D, 5);
 
         forcePollAndWaitForIdle();
@@ -552,8 +554,8 @@
         states = new NetworkState[] {buildMobile4gState(TEST_IFACE2)};
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
 
         mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
         forcePollAndWaitForIdle();
@@ -564,10 +566,10 @@
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
-                .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
-                .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+                .addEntry(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+                .addEntry(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L));
         mService.incrementOperationCount(UID_RED, 0xFAAD, 5);
 
         forcePollAndWaitForIdle();
@@ -591,9 +593,9 @@
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L));
         mService.incrementOperationCount(UID_RED, 0xF00D, 1);
 
         forcePollAndWaitForIdle();
@@ -608,9 +610,9 @@
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0L));
         forcePollAndWaitForIdle();
 
         // first verify entire history present
@@ -654,9 +656,9 @@
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
-                .addValues(entry1)
-                .addValues(entry2)
-                .addValues(entry3));
+                .addEntry(entry1)
+                .addEntry(entry2)
+                .addEntry(entry3));
         mService.incrementOperationCount(UID_RED, 0xF00D, 1);
 
         NetworkStats stats = mService.getDetailedUidStats(INTERFACES_ALL);
@@ -704,11 +706,11 @@
                 .thenReturn(augmentedIfaceFilter);
         when(mStatsFactory.readNetworkStatsDetail(eq(UID_ALL), any(), eq(TAG_ALL)))
                 .thenReturn(new NetworkStats(getElapsedRealtime(), 1)
-                        .addValues(uidStats));
+                        .addEntry(uidStats));
         when(mNetManager.getNetworkStatsTethering(STATS_PER_UID))
                 .thenReturn(new NetworkStats(getElapsedRealtime(), 2)
-                        .addValues(tetheredStats1)
-                        .addValues(tetheredStats2));
+                        .addEntry(tetheredStats1)
+                        .addEntry(tetheredStats2));
 
         NetworkStats stats = mService.getDetailedUidStats(ifaceFilter);
 
@@ -745,8 +747,8 @@
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L));
         mService.incrementOperationCount(UID_RED, 0xF00D, 1);
 
         forcePollAndWaitForIdle();
@@ -760,10 +762,10 @@
         expectDefaultSettings();
         expectNetworkStatsSummary(buildEmptyStats());
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L));
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L)
+                .addEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L));
         mService.setUidForeground(UID_RED, true);
         mService.incrementOperationCount(UID_RED, 0xFAAD, 1);
 
@@ -804,9 +806,9 @@
         // and DEFAULT_NETWORK_YES, because these three properties aren't tracked at that layer.
         // We layer them on top by inspecting the iface properties.
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 0L));
         mService.incrementOperationCount(UID_RED, 0xF00D, 1);
 
@@ -843,9 +845,9 @@
         // ROAMING_NO, because metered and roaming isn't tracked at that layer. We layer it
         // on top by inspecting the iface properties.
         expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO,
                         DEFAULT_NETWORK_YES,  128L, 2L, 128L, 2L, 0L)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_NO,
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_NO,
                         DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 0L));
         forcePollAndWaitForIdle();
 
@@ -885,10 +887,10 @@
 
         // Traffic for UID_RED.
         final NetworkStats uidStats = new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L);
+                .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L);
         // All tethering traffic, both hardware and software.
         final NetworkStats tetherStats = new NetworkStats(getElapsedRealtime(), 1)
-                .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L,
+                .addEntry(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L,
                         0L);
 
         expectNetworkStatsSummary(ifaceStats, tetherStatsHardware);
@@ -1001,6 +1003,88 @@
         mService.unregisterUsageRequest(unknownRequest);
     }
 
+    @Test
+    public void testStatsProviderUpdateStats() throws Exception {
+        // Pretend that network comes online.
+        expectDefaultSettings();
+        final NetworkState[] states = new NetworkState[]{buildWifiState(true /* isMetered */)};
+        expectNetworkStatsSummary(buildEmptyStats());
+        expectNetworkStatsUidDetail(buildEmptyStats());
+
+        // Register custom provider and retrieve callback.
+        final TestableNetworkStatsProvider provider = new TestableNetworkStatsProvider();
+        final INetworkStatsProviderCallback cb =
+                mService.registerNetworkStatsProvider("TEST", provider);
+        assertNotNull(cb);
+
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+
+        // Verifies that one requestStatsUpdate will be called during iface update.
+        provider.expectStatsUpdate(0 /* unused */);
+
+        // Create some initial traffic and report to the service.
+        incrementCurrentTime(HOUR_IN_MILLIS);
+        final NetworkStats expectedStats = new NetworkStats(0L, 1)
+                .addValues(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT,
+                        TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES,
+                        128L, 2L, 128L, 2L, 1L))
+                .addValues(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT,
+                        0xF00D, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES,
+                        64L, 1L, 64L, 1L, 1L));
+        cb.onStatsUpdated(0 /* unused */, expectedStats, expectedStats);
+
+        // Make another empty mutable stats object. This is necessary since the new NetworkStats
+        // object will be used to compare with the old one in NetworkStatsRecoder, two of them
+        // cannot be the same object.
+        expectNetworkStatsUidDetail(buildEmptyStats());
+
+        forcePollAndWaitForIdle();
+
+        // Verifies that one requestStatsUpdate and setAlert will be called during polling.
+        provider.expectStatsUpdate(0 /* unused */);
+        provider.expectSetAlert(MB_IN_BYTES);
+
+        // Verifies that service recorded history, does not verify uid tag part.
+        assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1);
+
+        // Verifies that onStatsUpdated updates the stats accordingly.
+        final NetworkStats stats = mSession.getSummaryForAllUid(
+                sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
+        assertEquals(2, stats.size());
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+                DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 1L);
+        assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
+                DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 1L);
+
+        // Verifies that unregister the callback will remove the provider from service.
+        cb.unregister();
+        forcePollAndWaitForIdle();
+        provider.assertNoCallback();
+    }
+
+    @Test
+    public void testStatsProviderSetAlert() throws Exception {
+        // Pretend that network comes online.
+        expectDefaultSettings();
+        NetworkState[] states = new NetworkState[]{buildWifiState(true /* isMetered */)};
+        mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+
+        // Register custom provider and retrieve callback.
+        final TestableNetworkStatsProvider provider = new TestableNetworkStatsProvider();
+        final INetworkStatsProviderCallback cb =
+                mService.registerNetworkStatsProvider("TEST", provider);
+        assertNotNull(cb);
+
+        // Simulates alert quota of the provider has been reached.
+        cb.onAlertReached();
+        HandlerUtilsKt.waitForIdle(mHandler, WAIT_TIMEOUT);
+
+        // Verifies that polling is triggered by alert reached.
+        provider.expectStatsUpdate(0 /* unused */);
+        // Verifies that global alert will be re-armed.
+        provider.expectSetAlert(MB_IN_BYTES);
+    }
+
     private static File getBaseDir(File statsDir) {
         File baseDir = new File(statsDir, "netstats");
         baseDir.mkdirs();
@@ -1102,6 +1186,7 @@
     private void expectSettings(long persistBytes, long bucketDuration, long deleteAge)
             throws Exception {
         when(mSettings.getPollInterval()).thenReturn(HOUR_IN_MILLIS);
+        when(mSettings.getPollDelay()).thenReturn(0L);
         when(mSettings.getSampleEnabled()).thenReturn(true);
 
         final Config config = new Config(bucketDuration, deleteAge, deleteAge);
diff --git a/tests/utils/testutils/Android.bp b/tests/utils/testutils/Android.bp
index f71be7b..a6625ab 100644
--- a/tests/utils/testutils/Android.bp
+++ b/tests/utils/testutils/Android.bp
@@ -22,6 +22,7 @@
     static_libs: [
         "junit",
         "hamcrest-library",
+        "androidx.test.runner",
     ],
 
     libs: [
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java b/tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
rename to tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java
index e2b517f..bce2ab5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
+++ b/tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.accessibility;
+package com.android.server.accessibility.test;
 
 import android.os.Handler;
 import android.os.Looper;
@@ -31,7 +31,7 @@
  * at their target.
  */
 public class MessageCapturingHandler extends Handler {
-    List<Pair<Message, Long>> timedMessages = new ArrayList<>();
+    public List<Pair<Message, Long>> timedMessages = new ArrayList<>();
 
     Handler.Callback mCallback;
 
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 4555caa..5b6935b 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -895,7 +895,7 @@
           // android:versionCode from the framework AndroidManifest.xml.
           ExtractCompileSdkVersions(asset_source->GetAssetManager());
         }
-      } else if (asset_source->IsPackageDynamic(entry.first)) {
+      } else if (asset_source->IsPackageDynamic(entry.first, entry.second)) {
         final_table_.included_packages_[entry.first] = entry.second;
       }
     }
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 83e20b5..897fa80 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -245,7 +245,8 @@
   return package_map;
 }
 
-bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId) const {
+bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId,
+    const std::string& package_name) const {
   if (packageId == 0) {
     return true;
   }
@@ -253,7 +254,7 @@
   for (const std::unique_ptr<const ApkAssets>& assets : apk_assets_) {
     for (const std::unique_ptr<const android::LoadedPackage>& loaded_package
          : assets->GetLoadedArsc()->GetPackages()) {
-      if (packageId == loaded_package->GetPackageId() && loaded_package->IsDynamic()) {
+      if (package_name == loaded_package->GetPackageName() && loaded_package->IsDynamic()) {
         return true;
       }
     }
@@ -328,12 +329,12 @@
   bool found = false;
   ResourceId res_id = 0;
   uint32_t type_spec_flags;
+  ResourceName real_name;
 
   // There can be mangled resources embedded within other packages. Here we will
   // look into each package and look-up the mangled name until we find the resource.
   asset_manager_.ForEachPackage([&](const std::string& package_name, uint8_t id) -> bool {
-    ResourceName real_name(name.package, name.type, name.entry);
-
+    real_name = ResourceName(name.package, name.type, name.entry);
     if (package_name != name.package) {
       real_name.entry = mangled_entry;
       real_name.package = package_name;
@@ -353,12 +354,12 @@
   }
 
   std::unique_ptr<SymbolTable::Symbol> s;
-  if (name.type == ResourceType::kAttr) {
+  if (real_name.type == ResourceType::kAttr) {
     s = LookupAttributeInTable(asset_manager_, res_id);
   } else {
     s = util::make_unique<SymbolTable::Symbol>();
     s->id = res_id;
-    s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id());
+    s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package);
   }
 
   if (s) {
@@ -406,7 +407,7 @@
   } else {
     s = util::make_unique<SymbolTable::Symbol>();
     s->id = id;
-    s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id());
+    s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package);
   }
 
   if (s) {
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index 6997cd6..06eaf63 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -194,7 +194,7 @@
 
   bool AddAssetPath(const android::StringPiece& path);
   std::map<size_t, std::string> GetAssignedPackageIds() const;
-  bool IsPackageDynamic(uint32_t packageId) const;
+  bool IsPackageDynamic(uint32_t packageId, const std::string& package_name) const;
 
   std::unique_ptr<SymbolTable::Symbol> FindByName(
       const ResourceName& name) override;
diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
index 46105f4..0b2077d 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists.py
@@ -149,7 +149,12 @@
         The package name of the class containing the field/method.
     """
     full_class_name = signature.split(";->")[0]
-    package_name = full_class_name[1:full_class_name.rindex("/")]
+    # Example: Landroid/hardware/radio/V1_2/IRadio$Proxy
+    if (full_class_name[0] != "L"):
+        raise ValueError("Expected to start with 'L': %s" % full_class_name)
+    full_class_name = full_class_name[1:]
+    # If full_class_name doesn't contain '/', then package_name will be ''.
+    package_name = full_class_name.rpartition("/")[0]
     return package_name.replace('/', '.')
 
 class FlagsDict:
diff --git a/tools/lock_agent/Android.bp b/tools/lock_agent/Android.bp
index 79dce4a..7b2ca9a 100644
--- a/tools/lock_agent/Android.bp
+++ b/tools/lock_agent/Android.bp
@@ -25,6 +25,7 @@
     srcs: ["agent.cpp"],
     static_libs: [
         "libbase",
+        "liblog",
         "libz",
         "slicer",
     ],
diff --git a/tools/stats_log_api_gen/java_writer.cpp b/tools/stats_log_api_gen/java_writer.cpp
index 7f0872c..7fa47f6 100644
--- a/tools/stats_log_api_gen/java_writer.cpp
+++ b/tools/stats_log_api_gen/java_writer.cpp
@@ -85,8 +85,9 @@
         string indent("");
         if (supportQ) {
             // TODO(b/146235828): Use just SDK_INT check once it is incremented from Q.
-            fprintf(out, "        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q ||\n");
-            fprintf(out, "                Build.VERSION.CODENAME.equals(\"R\")) {\n");
+            fprintf(out, "        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q\n");
+            fprintf(out, "                || (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q\n");
+            fprintf(out, "                    && Build.VERSION.PREVIEW_SDK_INT > 0)) {\n");
             indent = "    ";
         }
 
diff --git a/wifi/Android.bp b/wifi/Android.bp
index 634f674..6326f14 100644
--- a/wifi/Android.bp
+++ b/wifi/Android.bp
@@ -32,6 +32,7 @@
         // framework-wifi.jar. This is not a good idea, should move WifiNetworkScoreCache
         // to a separate package.
         "java/android/net/wifi/WifiNetworkScoreCache.java",
+        "java/android/net/wifi/WifiOemConfigStoreMigrationHook.java",
         "java/android/net/wifi/wificond/*.java",
         ":libwificond_ipc_aidl",
     ],
@@ -51,26 +52,56 @@
     "//external/robolectric-shadows:__subpackages__",
 ]
 
+// wifi-service needs pre-jarjared version of framework-wifi so it can reference copied utility
+// classes before they are renamed.
 java_library {
-    name: "framework-wifi",
+    name: "framework-wifi-pre-jarjar",
     // TODO(b/140299412) should be core_current once we build against framework-system-stubs
     sdk_version: "core_platform",
+    static_libs: [
+        "framework-wifi-util-lib",
+        "android.hardware.wifi-V1.0-java-constants",
+    ],
     libs: [
         // TODO(b/140299412) should be framework-system-stubs once we fix all @hide dependencies
         "framework-minus-apex",
-        "unsupportedappusage",
+        "framework-annotations-lib",
+        "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
+        "unsupportedappusage-annotation", // for dalvik.annotation.compat.UnsupportedAppUsage
     ],
     srcs: [
         ":framework-wifi-updatable-sources",
     ],
+    installable: false,
+    visibility: [
+        "//frameworks/opt/net/wifi/service",
+        "//frameworks/opt/net/wifi/tests/wifitests",
+    ],
+}
+
+// post-jarjar version of framework-wifi
+java_library {
+    name: "framework-wifi",
+    // TODO(b/140299412) should be core_current once we build against framework-system-stubs
+    sdk_version: "core_platform",
+    static_libs: [
+        "framework-wifi-pre-jarjar",
+    ],
+    jarjar_rules: ":wifi-jarjar-rules",
+
     installable: true,
     optimize: {
         enabled: false
     },
+    hostdex: true, // for hiddenapi check
     visibility: [
         "//frameworks/base", // TODO(b/140299412) remove once all dependencies are fixed
         "//frameworks/opt/net/wifi/service:__subpackages__",
     ] + test_access_hidden_api_whitelist,
+    apex_available: [
+        "com.android.wifi",
+        "test_com.android.wifi",
+    ],
     plugins: ["java_api_finder"],
 }
 
@@ -106,11 +137,18 @@
     name: "framework-wifi-test-defaults",
     sdk_version: "core_platform", // tests can use @CorePlatformApi's
     libs: [
+        // order matters: classes in framework-wifi are resolved before framework, meaning
+        // @hide APIs in framework-wifi are resolved before @SystemApi stubs in framework
         "framework-wifi",
-        "framework-minus-apex",
+        "framework",
 
         // if sdk_version="" this gets automatically included, but here we need to add manually.
         "framework-res",
     ],
     visibility: test_access_hidden_api_whitelist,
 }
+
+filegroup {
+    name: "wifi-jarjar-rules",
+    srcs: ["jarjar-rules.txt"],
+}
diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt
new file mode 100644
index 0000000..0746d62
--- /dev/null
+++ b/wifi/jarjar-rules.txt
@@ -0,0 +1,39 @@
+rule android.net.InterfaceConfigurationParcel* @0
+rule android.net.InterfaceConfiguration* com.android.server.x.wifi.net.InterfaceConfiguration@1
+
+# We don't jar-jar the entire package because, we still use some classes (like
+# AsyncChannel in com.android.internal.util) from these packages which are not
+# inside our jar (currently in framework.jar, but will be in wifisdk.jar in the future).
+rule com.android.internal.util.FastXmlSerializer* com.android.server.x.wifi.util.FastXmlSerializer@1
+rule com.android.internal.util.HexDump* com.android.server.x.wifi.util.HexDump@1
+rule com.android.internal.util.IState* com.android.server.x.wifi.util.IState@1
+rule com.android.internal.util.MessageUtils* com.android.server.x.wifi.util.MessageUtils@1
+rule com.android.internal.util.State* com.android.server.x.wifi.util.State@1
+rule com.android.internal.util.StateMachine* com.android.server.x.wifi.util.StateMachine@1
+rule com.android.internal.util.WakeupMessage* com.android.server.x.wifi.util.WakeupMessage@1
+rule com.android.internal.util.XmlUtils* com.android.server.x.wifi.util.XmlUtils@1
+
+rule android.util.BackupUtils* com.android.server.x.wifi.util.BackupUtils@1
+rule android.util.LocalLog* com.android.server.x.wifi.util.LocalLog@1
+rule android.util.Rational* com.android.server.x.wifi.util.Rational@1
+
+rule android.os.BasicShellCommandHandler* com.android.server.x.wifi.os.BasicShellCommandHandler@1
+
+# Use our statically linked bouncy castle library
+rule org.bouncycastle.** com.android.server.x.wifi.bouncycastle.@1
+# Use our statically linked protobuf library
+rule com.google.protobuf.** com.android.server.x.wifi.protobuf.@1
+# Use our statically linked PlatformProperties library
+rule android.sysprop.** com.android.server.x.wifi.sysprop.@1
+
+
+# used by both framework-wifi and wifi-service
+rule android.content.pm.BaseParceledListSlice* android.x.net.wifi.util.BaseParceledListSlice@1
+rule android.content.pm.ParceledListSlice* android.x.net.wifi.util.ParceledListSlice@1
+rule android.net.shared.Inet4AddressUtils* android.x.net.wifi.util.Inet4AddressUtils@1
+rule android.os.HandlerExecutor* android.x.net.wifi.util.HandlerExecutor@1
+rule android.telephony.Annotation* android.x.net.wifi.util.TelephonyAnnotation@1
+rule com.android.internal.util.AsyncChannel* android.x.net.wifi.util.AsyncChannel@1
+rule com.android.internal.util.AsyncService* android.x.net.wifi.util.AsyncService@1
+rule com.android.internal.util.Preconditions* android.x.net.wifi.util.Preconditions@1
+rule com.android.internal.util.Protocol* android.x.net.wifi.util.Protocol@1
diff --git a/wifi/java/android/net/wifi/ISoftApCallback.aidl b/wifi/java/android/net/wifi/ISoftApCallback.aidl
index 482b491..f81bcb9 100644
--- a/wifi/java/android/net/wifi/ISoftApCallback.aidl
+++ b/wifi/java/android/net/wifi/ISoftApCallback.aidl
@@ -55,9 +55,17 @@
 
 
     /**
-     * Service to manager callback providing information of softap.
+     * Service to manager callback providing capability of softap.
      *
      * @param capability is the softap capability. {@link SoftApCapability}
      */
     void onCapabilityChanged(in SoftApCapability capability);
+
+    /**
+     * Service to manager callback providing blocked client of softap with specific reason code.
+     *
+     * @param client the currently blocked client.
+     * @param blockedReason one of blocked reason from {@link WifiManager.SapClientBlockedReason}
+     */
+    void onBlockedClientConnecting(in WifiClient client, int blockedReason);
 }
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 1678d5a..f490766 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -90,6 +90,8 @@
 
     void allowAutojoin(int netId, boolean choice);
 
+    void allowAutojoinPasspoint(String fqdn, boolean enableAutoJoin);
+
     boolean startScan(String packageName, String featureId);
 
     List<ScanResult> getScanResults(String callingPackage, String callingFeatureId);
@@ -248,4 +250,6 @@
     void unregisterSuggestionConnectionStatusListener(int listenerIdentifier, String packageName);
 
     int calculateSignalLevel(int rssi);
+
+    List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(in List<ScanResult> scanResults);
 }
diff --git a/wifi/java/android/net/wifi/SoftApCapability.java b/wifi/java/android/net/wifi/SoftApCapability.java
index c4474e2..2bbe7d2 100644
--- a/wifi/java/android/net/wifi/SoftApCapability.java
+++ b/wifi/java/android/net/wifi/SoftApCapability.java
@@ -61,11 +61,20 @@
      */
     public static final int SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT = 1 << 1;
 
+
+    /**
+     * Support for WPA3 Simultaneous Authentication of Equals (WPA3-SAE).
+     *
+     * flag when {@link config_wifi_softap_sae_supported)} is true.
+     */
+    public static final int SOFTAP_FEATURE_WPA3_SAE = 1 << 2;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = { "SOFTAP_FEATURE_" }, value = {
             SOFTAP_FEATURE_ACS_OFFLOAD,
             SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT,
+            SOFTAP_FEATURE_WPA3_SAE,
     })
     public @interface HotspotFeatures {}
 
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index 05e245b..a77d30a 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -25,12 +25,16 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.nio.charset.CharsetEncoder;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -55,6 +59,11 @@
 @SystemApi
 public final class SoftApConfiguration implements Parcelable {
 
+    @VisibleForTesting
+    static final int PSK_MIN_LEN = 8;
+    @VisibleForTesting
+    static final int PSK_MAX_LEN = 63;
+
     /**
      * 2GHz band.
      * @hide
@@ -142,9 +151,10 @@
     private final @Nullable MacAddress mBssid;
 
     /**
-     * Pre-shared key for WPA2-PSK encryption (non-null enables WPA2-PSK).
+     * Pre-shared key for WPA2-PSK or WPA3-SAE-Transition or WPA3-SAE encryption which depends on
+     * the security type.
      */
-    private final @Nullable String mWpa2Passphrase;
+    private final @Nullable String mPassphrase;
 
     /**
      * This is a network that does not broadcast its SSID, so an
@@ -175,6 +185,28 @@
     private final @SecurityType int mSecurityType;
 
     /**
+     * The flag to indicate client need to authorize by user
+     * when client is connecting to AP.
+     */
+    private final boolean mClientControlByUser;
+
+    /**
+     * The list of blocked client that can't associate to the AP.
+     */
+    private final List<MacAddress> mBlockedClientList;
+
+    /**
+     * The list of allowed client that can associate to the AP.
+     */
+    private final List<MacAddress> mAllowedClientList;
+
+    /**
+     * Delay in milliseconds before shutting down soft AP when
+     * there are no connected devices.
+     */
+    private final int mShutdownTimeoutMillis;
+
+    /**
      * Security types we support.
      */
     /** @hide */
@@ -186,25 +218,41 @@
     public static final int SECURITY_TYPE_WPA2_PSK = 1;
 
     /** @hide */
+    @SystemApi
+    public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2;
+
+    /** @hide */
+    @SystemApi
+    public static final int SECURITY_TYPE_WPA3_SAE = 3;
+
+    /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = { "SECURITY_TYPE" }, value = {
+    @IntDef(prefix = { "SECURITY_TYPE_" }, value = {
         SECURITY_TYPE_OPEN,
         SECURITY_TYPE_WPA2_PSK,
+        SECURITY_TYPE_WPA3_SAE_TRANSITION,
+        SECURITY_TYPE_WPA3_SAE,
     })
     public @interface SecurityType {}
 
     /** 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, int maxNumberOfClients) {
+            @Nullable String passphrase, boolean hiddenSsid, @BandType int band, int channel,
+            @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis,
+            boolean clientControlByUser, @NonNull List<MacAddress> blockedList,
+            @NonNull List<MacAddress> allowedList) {
         mSsid = ssid;
         mBssid = bssid;
-        mWpa2Passphrase = wpa2Passphrase;
+        mPassphrase = passphrase;
         mHiddenSsid = hiddenSsid;
         mBand = band;
         mChannel = channel;
         mSecurityType = securityType;
         mMaxNumberOfClients = maxNumberOfClients;
+        mShutdownTimeoutMillis = shutdownTimeoutMillis;
+        mClientControlByUser = clientControlByUser;
+        mBlockedClientList = new ArrayList<>(blockedList);
+        mAllowedClientList = new ArrayList<>(allowedList);
     }
 
     @Override
@@ -218,18 +266,23 @@
         SoftApConfiguration other = (SoftApConfiguration) otherObj;
         return Objects.equals(mSsid, other.mSsid)
                 && Objects.equals(mBssid, other.mBssid)
-                && Objects.equals(mWpa2Passphrase, other.mWpa2Passphrase)
+                && Objects.equals(mPassphrase, other.mPassphrase)
                 && mHiddenSsid == other.mHiddenSsid
                 && mBand == other.mBand
                 && mChannel == other.mChannel
                 && mSecurityType == other.mSecurityType
-                && mMaxNumberOfClients == other.mMaxNumberOfClients;
+                && mMaxNumberOfClients == other.mMaxNumberOfClients
+                && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis
+                && mClientControlByUser == other.mClientControlByUser
+                && Objects.equals(mBlockedClientList, other.mBlockedClientList)
+                && Objects.equals(mAllowedClientList, other.mAllowedClientList);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mSsid, mBssid, mWpa2Passphrase, mHiddenSsid,
-                mBand, mChannel, mSecurityType, mMaxNumberOfClients);
+        return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid,
+                mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis,
+                mClientControlByUser, mBlockedClientList, mAllowedClientList);
     }
 
     @Override
@@ -237,13 +290,17 @@
         StringBuilder sbuf = new StringBuilder();
         sbuf.append("ssid=").append(mSsid);
         if (mBssid != null) sbuf.append(" \n bssid=").append(mBssid.toString());
-        sbuf.append(" \n Wpa2Passphrase =").append(
-                TextUtils.isEmpty(mWpa2Passphrase) ? "<empty>" : "<non-empty>");
+        sbuf.append(" \n Passphrase =").append(
+                TextUtils.isEmpty(mPassphrase) ? "<empty>" : "<non-empty>");
         sbuf.append(" \n HiddenSsid =").append(mHiddenSsid);
         sbuf.append(" \n Band =").append(mBand);
         sbuf.append(" \n Channel =").append(mChannel);
         sbuf.append(" \n SecurityType=").append(getSecurityType());
         sbuf.append(" \n MaxClient=").append(mMaxNumberOfClients);
+        sbuf.append(" \n ShutdownTimeoutMillis=").append(mShutdownTimeoutMillis);
+        sbuf.append(" \n ClientControlByUser=").append(mClientControlByUser);
+        sbuf.append(" \n BlockedClientList=").append(mBlockedClientList);
+        sbuf.append(" \n AllowedClientList=").append(mAllowedClientList);
         return sbuf.toString();
     }
 
@@ -251,12 +308,16 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(mSsid);
         dest.writeParcelable(mBssid, flags);
-        dest.writeString(mWpa2Passphrase);
+        dest.writeString(mPassphrase);
         dest.writeBoolean(mHiddenSsid);
         dest.writeInt(mBand);
         dest.writeInt(mChannel);
         dest.writeInt(mSecurityType);
         dest.writeInt(mMaxNumberOfClients);
+        dest.writeInt(mShutdownTimeoutMillis);
+        dest.writeBoolean(mClientControlByUser);
+        dest.writeTypedList(mBlockedClientList);
+        dest.writeTypedList(mAllowedClientList);
     }
 
     @Override
@@ -272,7 +333,9 @@
                     in.readString(),
                     in.readParcelable(MacAddress.class.getClassLoader()),
                     in.readString(), in.readBoolean(), in.readInt(), in.readInt(), in.readInt(),
-                    in.readInt());
+                    in.readInt(), in.readInt(), in.readBoolean(),
+                    in.createTypedArrayList(MacAddress.CREATOR),
+                    in.createTypedArrayList(MacAddress.CREATOR));
         }
 
         @Override
@@ -300,12 +363,12 @@
     }
 
     /**
-     * Returns String set to be passphrase for the WPA2-PSK AP.
-     * {@link Builder#setWpa2Passphrase(String)}.
+     * Returns String set to be passphrase for current AP.
+     * {@link #setPassphrase(String, @SecurityType int)}.
      */
     @Nullable
-    public String getWpa2Passphrase() {
-        return mWpa2Passphrase;
+    public String getPassphrase() {
+        return mPassphrase;
     }
 
     /**
@@ -351,6 +414,43 @@
     }
 
     /**
+     * Returns the shutdown timeout in milliseconds.
+     * The Soft AP will shutdown when there are no devices associated to it for
+     * the timeout duration. See {@link Builder#setShutdownTimeoutMillis(int)}.
+     */
+    public int getShutdownTimeoutMillis() {
+        return mShutdownTimeoutMillis;
+    }
+
+    /**
+     * Returns a flag indicating whether clients need to be pre-approved by the user.
+     * (true: authorization required) or not (false: not required).
+     * {@link Builder#enableClientControlByUser(Boolean)}.
+     */
+    public boolean isClientControlByUserEnabled() {
+        return mClientControlByUser;
+    }
+
+    /**
+     * Returns List of clients which aren't allowed to associate to the AP.
+     *
+     * Clients are configured using {@link Builder#setClientList(List, List)}
+     */
+    @NonNull
+    public List<MacAddress> getBlockedClientList() {
+        return mBlockedClientList;
+    }
+
+    /**
+     * List of clients which are allowed to associate to the AP.
+     * Clients are configured using {@link Builder#setClientList(List, List)}
+     */
+    @NonNull
+    public List<MacAddress> getAllowedClientList() {
+        return mAllowedClientList;
+    }
+
+    /**
      * Builds a {@link SoftApConfiguration}, which allows an app to configure various aspects of a
      * Soft AP.
      *
@@ -360,23 +460,16 @@
     public static final class Builder {
         private String mSsid;
         private MacAddress mBssid;
-        private String mWpa2Passphrase;
+        private String mPassphrase;
         private boolean mHiddenSsid;
         private int mBand;
         private int mChannel;
         private int mMaxNumberOfClients;
-
-        private int setSecurityType() {
-            int securityType = SECURITY_TYPE_OPEN;
-            if (!TextUtils.isEmpty(mWpa2Passphrase)) { // WPA2-PSK network.
-                securityType = SECURITY_TYPE_WPA2_PSK;
-            }
-            return securityType;
-        }
-
-        private void clearAllPassphrase() {
-            mWpa2Passphrase = null;
-        }
+        private int mSecurityType;
+        private int mShutdownTimeoutMillis;
+        private boolean mClientControlByUser;
+        private List<MacAddress> mBlockedClientList;
+        private List<MacAddress> mAllowedClientList;
 
         /**
          * Constructs a Builder with default values (see {@link Builder}).
@@ -384,11 +477,16 @@
         public Builder() {
             mSsid = null;
             mBssid = null;
-            mWpa2Passphrase = null;
+            mPassphrase = null;
             mHiddenSsid = false;
             mBand = BAND_2GHZ;
             mChannel = 0;
             mMaxNumberOfClients = 0;
+            mSecurityType = SECURITY_TYPE_OPEN;
+            mShutdownTimeoutMillis = 0;
+            mClientControlByUser = false;
+            mBlockedClientList = new ArrayList<>();
+            mAllowedClientList = new ArrayList<>();
         }
 
         /**
@@ -399,11 +497,16 @@
 
             mSsid = other.mSsid;
             mBssid = other.mBssid;
-            mWpa2Passphrase = other.mWpa2Passphrase;
+            mPassphrase = other.mPassphrase;
             mHiddenSsid = other.mHiddenSsid;
             mBand = other.mBand;
             mChannel = other.mChannel;
             mMaxNumberOfClients = other.mMaxNumberOfClients;
+            mSecurityType = other.mSecurityType;
+            mShutdownTimeoutMillis = other.mShutdownTimeoutMillis;
+            mClientControlByUser = other.mClientControlByUser;
+            mBlockedClientList = new ArrayList<>(other.mBlockedClientList);
+            mAllowedClientList = new ArrayList<>(other.mAllowedClientList);
         }
 
         /**
@@ -413,8 +516,10 @@
          */
         @NonNull
         public SoftApConfiguration build() {
-            return new SoftApConfiguration(mSsid, mBssid, mWpa2Passphrase,
-                mHiddenSsid, mBand, mChannel, setSecurityType(), mMaxNumberOfClients);
+            return new SoftApConfiguration(mSsid, mBssid, mPassphrase,
+                    mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients,
+                    mShutdownTimeoutMillis, mClientControlByUser, mBlockedClientList,
+                    mAllowedClientList);
         }
 
         /**
@@ -462,26 +567,43 @@
         }
 
         /**
-         * Specifies that this AP should use WPA2-PSK with the given ASCII WPA2 passphrase.
-         * When set to null, an open network is created.
-         * <p>
+         * Specifies that this AP should use specific security type with the given ASCII passphrase.
          *
-         * @param passphrase The passphrase to use, or null to unset a previously-set WPA2-PSK
-         *                   configuration.
+         * @param securityType one of the security types from {@link @SecurityType}.
+         * @param passphrase The passphrase to use for sepcific {@link @SecurityType} configuration
+         * or null with {@link @SecurityType#SECURITY_TYPE_OPEN}.
+         *
          * @return Builder for chaining.
-         * @throws IllegalArgumentException when the passphrase is the empty string
+         * @throws IllegalArgumentException when the passphrase length is invalid and
+         *         {@code securityType} is not {@link @SecurityType#SECURITY_TYPE_OPEN}
+         *         or non-null passphrase and {@code securityType} is
+         *         {@link @SecurityType#SECURITY_TYPE_OPEN}.
          */
         @NonNull
-        public Builder setWpa2Passphrase(@Nullable String passphrase) {
-            if (passphrase != null) {
+        public Builder setPassphrase(@Nullable String passphrase, @SecurityType int securityType) {
+            if (securityType == SECURITY_TYPE_OPEN) {
+                if (passphrase != null) {
+                    throw new IllegalArgumentException(
+                            "passphrase should be null when security type is open");
+                }
+            } else {
+                Preconditions.checkStringNotEmpty(passphrase);
                 final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
                 if (!asciiEncoder.canEncode(passphrase)) {
                     throw new IllegalArgumentException("passphrase not ASCII encodable");
                 }
-                Preconditions.checkStringNotEmpty(passphrase);
+                if (securityType == SECURITY_TYPE_WPA2_PSK
+                        || securityType == SECURITY_TYPE_WPA3_SAE_TRANSITION) {
+                    if (passphrase.length() < PSK_MIN_LEN || passphrase.length() > PSK_MAX_LEN) {
+                        throw new IllegalArgumentException(
+                                "Password size must be at least " + PSK_MIN_LEN
+                                + " and no more than " + PSK_MAX_LEN
+                                + " for WPA2_PSK and WPA3_SAE_TRANSITION Mode");
+                    }
+                }
             }
-            clearAllPassphrase();
-            mWpa2Passphrase = passphrase;
+            mSecurityType = securityType;
+            mPassphrase = passphrase;
             return this;
         }
 
@@ -589,5 +711,107 @@
             mMaxNumberOfClients = maxNumberOfClients;
             return this;
         }
+
+        /**
+         * Specifies the shutdown timeout in milliseconds.
+         * The Soft AP will shut down when there are no devices connected to it for
+         * the timeout duration.
+         *
+         * Specify a value of 0 to have the framework automatically use default timeout
+         * setting which defined in {@link R.integer.config_wifi_framework_soft_ap_timeout_delay}
+         *
+         * <p>
+         * <li>If not set, defaults to 0</li>
+         * <li>The shut down timout will apply when
+         * {@link Settings.Global.SOFT_AP_TIMEOUT_ENABLED} is true</li>
+         *
+         * @param timeoutMillis milliseconds of the timeout delay.
+         * @return Builder for chaining.
+         */
+        @NonNull
+        public Builder setShutdownTimeoutMillis(int timeoutMillis) {
+            if (timeoutMillis < 0) {
+                throw new IllegalArgumentException("Invalid timeout value");
+            }
+            mShutdownTimeoutMillis = timeoutMillis;
+            return this;
+        }
+
+        /**
+         * Configure the Soft AP to require manual user control of client association.
+         * If disabled (the default) then any client can associate to this Soft AP using the
+         * correct credentials until the Soft AP capacity is reached (capacity is hardware, carrier,
+         * or user limited - using {@link #setMaxNumberOfClients(int)}).
+         *
+         * If manual user control is enabled then clients will be accepted, rejected, or require
+         * a user approval based on the configuration provided by
+         * {@link #setClientList(List, List)}.
+         *
+         * <p>
+         * This method requires hardware support. Hardware support can be determined using
+         * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
+         * {@link SoftApCapability#isFeatureSupported(int)}
+         * with {@link SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT}
+         *
+         * <p>
+         * If the method is called on a device without hardware support then starting the soft AP
+         * using {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will fail with
+         * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
+         *
+         * <p>
+         * <li>If not set, defaults to false (i.e The authoriztion is not required).</li>
+         *
+         * @param enabled true for enabling the control by user, false otherwise.
+         * @return Builder for chaining.
+         */
+        @NonNull
+        public Builder enableClientControlByUser(boolean enabled) {
+            mClientControlByUser = enabled;
+            return this;
+        }
+
+
+        /**
+         * This method together with {@link enableClientControlByUser(boolean)} control client
+         * connections to the AP. If {@link enableClientControlByUser(false)} is configured than
+         * this API has no effect and clients are allowed to associate to the AP (within limit of
+         * max number of clients).
+         *
+         * If {@link enableClientControlByUser(true)} is configured then this API configures
+         * 2 lists:
+         * <ul>
+         * <li>List of clients which are blocked. These are rejected.</li>
+         * <li>List of clients which are explicitly allowed. These are auto-accepted.</li>
+         * </ul>
+         *
+         * <p>
+         * All other clients which attempt to associate, whose MAC addresses are on neither list,
+         * are:
+         * <ul>
+         * <li>Rejected</li>
+         * <li>A callback {@link WifiManager.SoftApCallback#onBlockedClientConnecting(WifiClient)}
+         * is issued (which allows the user to add them to the allowed client list if desired).<li>
+         * </ul>
+         *
+         * @param blockedClientList list of clients which are not allowed to associate to the AP.
+         * @param allowedClientList list of clients which are allowed to associate to the AP
+         *                          without user pre-approval.
+         * @return Builder for chaining.
+         */
+        @NonNull
+        public Builder setClientList(@NonNull List<MacAddress> blockedClientList,
+                @NonNull List<MacAddress> allowedClientList) {
+            mBlockedClientList = new ArrayList<>(blockedClientList);
+            mAllowedClientList = new ArrayList<>(allowedClientList);
+            Iterator<MacAddress> iterator = mAllowedClientList.iterator();
+            while (iterator.hasNext()) {
+                MacAddress client = iterator.next();
+                int index = mBlockedClientList.indexOf(client);
+                if (index != -1) {
+                    throw new IllegalArgumentException("A MacAddress exist in both list");
+                }
+            }
+            return this;
+        }
     }
 }
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index a379c75..f4c5b91 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -253,9 +253,12 @@
         /** LEAP/Network EAP (only used with LEAP) */
         public static final int LEAP = 2;
 
+        /** SAE (Used only for WPA3-Personal) */
+        public static final int SAE = 3;
+
         public static final String varName = "auth_alg";
 
-        public static final String[] strings = { "OPEN", "SHARED", "LEAP" };
+        public static final String[] strings = { "OPEN", "SHARED", "LEAP", "SAE" };
     }
 
     /**
@@ -468,10 +471,13 @@
                 break;
             case SECURITY_TYPE_SAE:
                 allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
+                allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
+                allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
                 requirePMF = true;
                 break;
             case SECURITY_TYPE_EAP_SUITE_B:
                 allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SUITE_B_192);
+                allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.GCMP_256);
                 allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
                 allowedGroupManagementCiphers.set(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256);
                 // Note: allowedSuiteBCiphers bitset will be set by the service once the
@@ -480,6 +486,8 @@
                 break;
             case SECURITY_TYPE_OWE:
                 allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OWE);
+                allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
+                allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
                 requirePMF = true;
                 break;
             case SECURITY_TYPE_WAPI_PSK:
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 378c67b..a8a31eb 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -233,16 +233,20 @@
     public @interface SuggestionConnectionStatusCode {}
 
     /**
-     * Broadcast intent action indicating whether Wi-Fi scanning is allowed currently
-     * @hide
+     * Broadcast intent action indicating whether Wi-Fi scanning is currently available.
+     * Available extras:
+     * - {@link #EXTRA_SCAN_AVAILABLE}
      */
-    public static final String WIFI_SCAN_AVAILABLE = "wifi_scan_available";
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_WIFI_SCAN_AVAILABLE =
+            "android.net.wifi.action.WIFI_SCAN_AVAILABLE";
 
     /**
-     * Extra int indicating scan availability, WIFI_STATE_ENABLED and WIFI_STATE_DISABLED
-     * @hide
+     * A boolean extra indicating whether scanning is currently available.
+     * Sent in the broadcast {@link #ACTION_WIFI_SCAN_AVAILABLE}.
+     * Its value is true if scanning is currently available, false otherwise.
      */
-    public static final String EXTRA_SCAN_AVAILABLE = "scan_enabled";
+    public static final String EXTRA_SCAN_AVAILABLE = "android.net.wifi.extra.SCAN_AVAILABLE";
 
     /**
      * Broadcast intent action indicating that the credential of a Wi-Fi network
@@ -545,15 +549,22 @@
     public static final String EXTRA_WIFI_AP_STATE = "wifi_state";
 
     /**
-     * The look up key for an int that indicates why softAP started failed
-     * currently support general and no_channel
-     * @see #SAP_START_FAILURE_GENERAL
-     * @see #SAP_START_FAILURE_NO_CHANNEL
-     * @see #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION
+     * An extra containing the int error code for Soft AP start failure.
+     * Can be obtained from the {@link #WIFI_AP_STATE_CHANGED_ACTION} using
+     * {@link android.content.Intent#getIntExtra}.
+     * This extra will only be attached if {@link #EXTRA_WIFI_AP_STATE} is
+     * attached and is equal to {@link #WIFI_AP_STATE_FAILED}.
+     *
+     * The error code will be one of:
+     * {@link #SAP_START_FAILURE_GENERAL},
+     * {@link #SAP_START_FAILURE_NO_CHANNEL},
+     * {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}
      *
      * @hide
      */
-    public static final String EXTRA_WIFI_AP_FAILURE_REASON = "wifi_ap_error_code";
+    @SystemApi
+    public static final String EXTRA_WIFI_AP_FAILURE_REASON =
+            "android.net.wifi.extra.WIFI_AP_FAILURE_REASON";
     /**
      * The previous Wi-Fi state.
      *
@@ -659,7 +670,8 @@
     public @interface SapStartFailure {}
 
     /**
-     *  All other reasons for AP start failure besides {@link #SAP_START_FAILURE_NO_CHANNEL}.
+     *  All other reasons for AP start failure besides {@link #SAP_START_FAILURE_NO_CHANNEL} and
+     *  {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
      *
      *  @hide
      */
@@ -684,6 +696,37 @@
     @SystemApi
     public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2;
 
+
+    /** @hide */
+    @IntDef(flag = false, prefix = { "SAP_CLIENT_BLOCKED_REASON_" }, value = {
+        SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER,
+        SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SapClientBlockedReason {}
+
+    /**
+     *  If Soft Ap client is blocked, this reason code means that client doesn't exist in the
+     *  specified configuration {@link SoftApConfiguration.Builder#setClientList(List, List)}
+     *  and the {@link SoftApConfiguration.Builder#enableClientControlByUser(true)}
+     *  is configured as well.
+     *  @hide
+     */
+    @SystemApi
+    public static final int SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0;
+
+    /**
+     *  If Soft Ap client is blocked, this reason code means that no more clients can be
+     *  associated to this AP since it reached maximum capacity. The maximum capacity is
+     *  the minimum of {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)} and
+     *  {@link SoftApCapability#getMaxSupportedClients} which get from
+     *  {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)}.
+     *
+     *  @hide
+     */
+    @SystemApi
+    public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"IFACE_IP_MODE_"}, value = {
@@ -1359,6 +1402,36 @@
     }
 
     /**
+     * Retrieve a list of {@link WifiConfiguration} for available {@link WifiNetworkSuggestion}
+     * matching the given list of {@link ScanResult}.
+     *
+     * An available {@link WifiNetworkSuggestion} must satisfy:
+     * <ul>
+     * <li> Matching one of the {@link ScanResult} from the given list.
+     * <li> and {@link WifiNetworkSuggestion.Builder#setIsUserAllowedToManuallyConnect(boolean)} set
+     * to true.
+     * </ul>
+     *
+     * @param scanResults a list of scanResult.
+     * @return a list of @link WifiConfiguration} for available {@link WifiNetworkSuggestion}
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD
+    })
+    @NonNull
+    public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(
+            @NonNull List<ScanResult> scanResults) {
+        try {
+            return mService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(scanResults);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Returns a list of unique Hotspot 2.0 OSU (Online Sign-Up) providers associated with a given
      * list of ScanResult.
      *
@@ -3192,8 +3265,17 @@
     /**
      * Sets the Wi-Fi AP Configuration.
      *
+     * If the API is called while the soft AP is enabled, the configuration will apply to
+     * the current soft AP if the new configuration only includes
+     * {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)}
+     * or {@link SoftApConfiguration.Builder#setShutdownTimeoutMillis(int)}
+     * or {@link SoftApConfiguration.Builder#enableClientControlByUser(boolean)}
+     * or {@link SoftApConfiguration.Builder#setClientList(List, List)}.
+     *
+     * Otherwise, the configuration changes will be applied when the Soft AP is next started
+     * (the framework will not stop/start the AP).
+     *
      * @param softApConfig  A valid SoftApConfiguration specifying the configuration of the SAP.
-
      * @return {@code true} if the operation succeeded, {@code false} otherwise
      *
      * @hide
@@ -3423,7 +3505,8 @@
          *                      {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
          * @param failureReason reason when in failed state. One of
          *                      {@link #SAP_START_FAILURE_GENERAL},
-         *                      {@link #SAP_START_FAILURE_NO_CHANNEL}
+         *                      {@link #SAP_START_FAILURE_NO_CHANNEL},
+         *                      {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}
          */
         default void onStateChanged(@WifiApState int state, @SapStartFailure int failureReason) {}
 
@@ -3452,6 +3535,22 @@
             // Do nothing: can be updated to add SoftApCapability details (e.g. meximum supported
             // client number) to the UI.
         }
+
+        /**
+         * Called when client trying to connect but device blocked the client with specific reason.
+         *
+         * Can be used to ask user to update client to allowed list or blocked list
+         * when reason is {@link SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER}, or
+         * indicate the block due to maximum supported client number limitation when reason is
+         * {@link SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS}.
+         *
+         * @param client the currently blocked client.
+         * @param blockedReason one of blocked reason from {@link SapClientBlockedReason}
+         */
+        default void onBlockedClientConnecting(@NonNull WifiClient client,
+                @SapClientBlockedReason int blockedReason) {
+            // Do nothing: can be used to ask user to update client to allowed list or blocked list.
+        }
     }
 
     /**
@@ -3518,6 +3617,19 @@
                 mCallback.onCapabilityChanged(capability);
             });
         }
+
+        @Override
+        public void onBlockedClientConnecting(@NonNull WifiClient client, int blockedReason) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "SoftApCallbackProxy: onBlockedClientConnecting: client=" + client
+                        + " with reason = " + blockedReason);
+            }
+
+            Binder.clearCallingIdentity();
+            mExecutor.execute(() -> {
+                mCallback.onBlockedClientConnecting(client, blockedReason);
+            });
+        }
     }
 
     /**
@@ -4103,6 +4215,23 @@
     }
 
     /**
+     * Configure auto-join settings for a Passpoint profile.
+     *
+     * @param fqdn the FQDN (fully qualified domain name) of the passpoint profile.
+     * @param enableAutoJoin true to enable autojoin, false to disable autojoin.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    public void allowAutojoinPasspoint(@NonNull String fqdn, boolean enableAutoJoin) {
+        try {
+            mService.allowAutojoinPasspoint(fqdn, enableAutoJoin);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Disable an ephemeral network.
      *
      * @param ssid in the format of WifiConfiguration's SSID.
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 9c1475f..c0e0890 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -116,12 +116,12 @@
         /**
          * Whether this network is shared credential with user to allow user manually connect.
          */
-        private boolean mIsUserAllowed;
+        private boolean mIsSharedWithUser;
 
         /**
-         * Whether the setIsUserAllowedToManuallyConnect have been called.
+         * Whether the setCredentialSharedWithUser have been called.
          */
-        private boolean mIsUserAllowedBeenSet;
+        private boolean mIsSharedWithUserSet;
         /**
          * Pre-shared key for use with WAPI-PSK networks.
          */
@@ -146,8 +146,8 @@
             mIsAppInteractionRequired = false;
             mIsUserInteractionRequired = false;
             mIsMetered = false;
-            mIsUserAllowed = true;
-            mIsUserAllowedBeenSet = false;
+            mIsSharedWithUser = true;
+            mIsSharedWithUserSet = false;
             mPriority = UNASSIGNED_PRIORITY;
             mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
             mWapiPskPassphrase = null;
@@ -430,13 +430,13 @@
          * <li>If not set, defaults to true (i.e. allow user to manually connect) for secure
          * networks and false for open networks.</li>
          *
-         * @param isAllowed {@code true} to indicate that the credentials may be used by the user to
+         * @param isShared {@code true} to indicate that the credentials may be used by the user to
          *                              manually connect to the network, {@code false} otherwise.
          * @return Instance of {@link Builder} to enable chaining of the builder method.
          */
-        public @NonNull Builder setIsUserAllowedToManuallyConnect(boolean isAllowed) {
-            mIsUserAllowed = isAllowed;
-            mIsUserAllowedBeenSet = true;
+        public @NonNull Builder setCredentialSharedWithUser(boolean isShared) {
+            mIsSharedWithUser = isShared;
+            mIsSharedWithUserSet = true;
             return this;
         }
 
@@ -602,11 +602,11 @@
                 }
                 wifiConfiguration = buildWifiConfiguration();
                 if (wifiConfiguration.isOpenNetwork()) {
-                    if (mIsUserAllowedBeenSet && mIsUserAllowed) {
+                    if (mIsSharedWithUserSet && mIsSharedWithUser) {
                         throw new IllegalStateException("Open network should not be "
-                                + "setIsUserAllowedToManuallyConnect to true");
+                                + "setCredentialSharedWithUser to true");
                     }
-                    mIsUserAllowed = false;
+                    mIsSharedWithUser = false;
                 }
             }
 
@@ -615,7 +615,7 @@
                     mPasspointConfiguration,
                     mIsAppInteractionRequired,
                     mIsUserInteractionRequired,
-                    mIsUserAllowed);
+                    mIsSharedWithUser);
         }
     }
 
diff --git a/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java b/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java
new file mode 100755
index 0000000..642dcb9
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Class used to provide one time hooks for existing OEM devices to migrate their config store
+ * data to the wifi mainline module.
+ * <p>
+ * Note:
+ * <li> OEM's need to implement {@link #load()} only if their
+ * existing config store format or file locations differs from the vanilla AOSP implementation (
+ * which is what the wifi mainline module understands).
+ * </li>
+ * <li> The wifi mainline module will invoke {@link #load()}  method on every bootup, its
+ * the responsibility of the OEM implementation to ensure that this method returns non-null data
+ * only on the first bootup. Once the migration is done, the OEM can safely delete their config
+ * store files and then return null on any subsequent reboots. The first & only relevant invocation
+ * of {@link #load()} occurs when a previously released device upgrades to the wifi
+ * mainline module from an OEM implementation of the wifi stack.
+ * </li>
+ * @hide
+ */
+@SystemApi
+public final class WifiOemConfigStoreMigrationHook {
+    /**
+     * Container for all the wifi config data to migrate.
+     */
+    public static final class MigrationData implements Parcelable {
+        /**
+         * Builder to create instance of {@link MigrationData}.
+         */
+        public static final class Builder {
+            private List<WifiConfiguration> mUserSavedNetworkConfigurations;
+            private SoftApConfiguration mUserSoftApConfiguration;
+
+            public Builder() {
+                mUserSavedNetworkConfigurations = null;
+                mUserSoftApConfiguration = null;
+            }
+
+            /**
+             * Sets the list of all user's saved network configurations parsed from OEM config
+             * store files.
+             *
+             * @param userSavedNetworkConfigurations List of {@link WifiConfiguration} representing
+             *                                       the list of user's saved networks
+             * @return Instance of {@link Builder} to enable chaining of the builder method.
+             */
+            public @NonNull Builder setUserSavedNetworkConfigurations(
+                    @NonNull List<WifiConfiguration> userSavedNetworkConfigurations) {
+                checkNotNull(userSavedNetworkConfigurations);
+                mUserSavedNetworkConfigurations = userSavedNetworkConfigurations;
+                return this;
+            }
+
+            /**
+             * Sets the user's softap configuration parsed from OEM config store files.
+             *
+             * @param userSoftApConfiguration {@link SoftApConfiguration} representing user's
+             *                                SoftAp configuration
+             * @return Instance of {@link Builder} to enable chaining of the builder method.
+             */
+            public @NonNull Builder setUserSoftApConfiguration(
+                    @NonNull SoftApConfiguration userSoftApConfiguration) {
+                checkNotNull(userSoftApConfiguration);
+                mUserSoftApConfiguration  = userSoftApConfiguration;
+                return this;
+            }
+
+            /**
+             * Build an instance of {@link MigrationData}.
+             *
+             * @return Instance of {@link MigrationData}.
+             */
+            public @NonNull MigrationData build() {
+                return new MigrationData(mUserSavedNetworkConfigurations, mUserSoftApConfiguration);
+            }
+        }
+
+        private final List<WifiConfiguration> mUserSavedNetworkConfigurations;
+        private final SoftApConfiguration mUserSoftApConfiguration;
+
+        private MigrationData(
+                @Nullable List<WifiConfiguration> userSavedNetworkConfigurations,
+                @Nullable SoftApConfiguration userSoftApConfiguration) {
+            mUserSavedNetworkConfigurations = userSavedNetworkConfigurations;
+            mUserSoftApConfiguration = userSoftApConfiguration;
+        }
+
+        public static final @NonNull Parcelable.Creator<MigrationData> CREATOR =
+                new Parcelable.Creator<MigrationData>() {
+                    @Override
+                    public MigrationData createFromParcel(Parcel in) {
+                        List<WifiConfiguration> userSavedNetworkConfigurations =
+                                in.readArrayList(null);
+                        SoftApConfiguration userSoftApConfiguration = in.readParcelable(null);
+                        return new MigrationData(
+                                userSavedNetworkConfigurations, userSoftApConfiguration);
+                    }
+
+                    @Override
+                    public MigrationData[] newArray(int size) {
+                        return new MigrationData[size];
+                    }
+                };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeList(mUserSavedNetworkConfigurations);
+            dest.writeParcelable(mUserSoftApConfiguration, flags);
+        }
+
+        /**
+         * Returns list of all user's saved network configurations.
+         *
+         * Note: Only to be returned if there is any format change in how OEM persisted this info.
+         * @return List of {@link WifiConfiguration} representing the list of user's saved networks,
+         * or null if no migration necessary.
+         */
+        @Nullable
+        public List<WifiConfiguration> getUserSavedNetworkConfigurations() {
+            return mUserSavedNetworkConfigurations;
+        }
+
+        /**
+         * Returns user's softap configuration.
+         *
+         * Note: Only to be returned if there is any format change in how OEM persisted this info.
+         * @return {@link SoftApConfiguration} representing user's SoftAp configuration,
+         * or null if no migration necessary.
+         */
+        @Nullable
+        public SoftApConfiguration getUserSoftApConfiguration() {
+            return mUserSoftApConfiguration;
+        }
+    }
+
+    private WifiOemConfigStoreMigrationHook() { }
+
+    /**
+     * Load data from OEM's config store.
+     *
+     * @return Instance of {@link MigrationData} for migrating data, null if no
+     * migration is necessary.
+     */
+    @Nullable
+    public static MigrationData load() {
+        // Note: OEM's should add code to parse data from their config store format here!
+        return null;
+    }
+}
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 2c39c32a..4f602fa 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -833,6 +833,7 @@
      * delivered to the listener. It is possible that onFullResult will not be called for all
      * results of the first scan if the listener was registered during the scan.
      *
+     * @param executor the Executor on which to run the callback.
      * @param listener specifies the object to report events to. This object is also treated as a
      *                 key for this request, and must also be specified to cancel the request.
      *                 Multiple requests should also not share this object.
@@ -955,15 +956,32 @@
      * starts a single scan and reports results asynchronously
      * @param settings specifies various parameters for the scan; for more information look at
      * {@link ScanSettings}
-     * @param workSource WorkSource to blame for power usage
      * @param listener specifies the object to report events to. This object is also treated as a
      *                 key for this scan, and must also be specified to cancel the scan. Multiple
      *                 scans should also not share this object.
+     * @param workSource WorkSource to blame for power usage
      */
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
     public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) {
+        startScan(settings, null, listener, workSource);
+    }
+
+    /**
+     * starts a single scan and reports results asynchronously
+     * @param settings specifies various parameters for the scan; for more information look at
+     * {@link ScanSettings}
+     * @param executor the Executor on which to run the callback.
+     * @param listener specifies the object to report events to. This object is also treated as a
+     *                 key for this scan, and must also be specified to cancel the scan. Multiple
+     *                 scans should also not share this object.
+     * @param workSource WorkSource to blame for power usage
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    public void startScan(ScanSettings settings, @Nullable @CallbackExecutor Executor executor,
+            ScanListener listener, WorkSource workSource) {
         Objects.requireNonNull(listener, "listener cannot be null");
-        int key = addListener(listener);
+        int key = addListener(listener, executor);
         if (key == INVALID_KEY) return;
         validateChannel();
         Bundle scanParams = new Bundle();
@@ -1029,16 +1047,17 @@
      * {@link ScanSettings}
      * @param pnoSettings specifies various parameters for PNO; for more information look at
      * {@link PnoSettings}
+     * @param executor the Executor on which to run the callback.
      * @param listener specifies the object to report events to. This object is also treated as a
      *                 key for this scan, and must also be specified to cancel the scan. Multiple
      *                 scans should also not share this object.
      * {@hide}
      */
     public void startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
-            PnoScanListener listener) {
+            @NonNull @CallbackExecutor Executor executor, PnoScanListener listener) {
         Objects.requireNonNull(listener, "listener cannot be null");
         Objects.requireNonNull(pnoSettings, "pnoSettings cannot be null");
-        int key = addListener(listener);
+        int key = addListener(listener, executor);
         if (key == INVALID_KEY) return;
         validateChannel();
         pnoSettings.isConnected = true;
@@ -1057,10 +1076,10 @@
      */
     @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
     public void startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
-            PnoScanListener listener) {
+            @NonNull @CallbackExecutor Executor executor, PnoScanListener listener) {
         Objects.requireNonNull(listener, "listener cannot be null");
         Objects.requireNonNull(pnoSettings, "pnoSettings cannot be null");
-        int key = addListener(listener);
+        int key = addListener(listener, executor);
         if (key == INVALID_KEY) return;
         validateChannel();
         pnoSettings.isConnected = false;
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 5befb54..1822e84 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -17,6 +17,7 @@
 package android.net.wifi.hotspot2;
 
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSp;
 import android.net.wifi.hotspot2.pps.Policy;
@@ -423,6 +424,41 @@
     }
 
     /**
+     * The auto-join configuration specifies whether or not the Passpoint Configuration is
+     * considered for auto-connection. If true then yes, if false then it isn't considered as part
+     * of auto-connection - but can still be manually connected to.
+     */
+    private boolean mIsAutoJoinEnabled = true;
+
+    /**
+     * Configures the auto-association status of this Passpoint configuration. A value of true
+     * indicates that the configuration will be considered for auto-connection, a value of false
+     * indicates that only manual connection will work - the framework will not auto-associate to
+     * this Passpoint network.
+     *
+     * @param autoJoinEnabled true to be considered for framework auto-connection, false otherwise.
+     * @hide
+     */
+    public void setAutoJoinEnabled(boolean autoJoinEnabled) {
+        mIsAutoJoinEnabled = autoJoinEnabled;
+    }
+
+    /**
+     * Indicates whether the Passpoint configuration may be auto-connected to by the framework. A
+     * value of true indicates that auto-connection can happen, a value of false indicates that it
+     * cannot. However, even when auto-connection is not possible manual connection by the user is
+     * possible.
+     *
+     * @return the auto-join configuration: true for auto-connection (or join) enabled, false
+     * otherwise.
+     * @hide
+     */
+    @SystemApi
+    public boolean isAutoJoinEnabled() {
+        return mIsAutoJoinEnabled;
+    }
+
+    /**
      * Constructor for creating PasspointConfiguration with default values.
      */
     public PasspointConfiguration() {}
@@ -464,6 +500,7 @@
         mServiceFriendlyNames = source.mServiceFriendlyNames;
         mAaaServerTrustedNames = source.mAaaServerTrustedNames;
         mCarrierId = source.mCarrierId;
+        mIsAutoJoinEnabled = source.mIsAutoJoinEnabled;
     }
 
     @Override
@@ -493,6 +530,7 @@
                 (HashMap<String, String>) mServiceFriendlyNames);
         dest.writeBundle(bundle);
         dest.writeInt(mCarrierId);
+        dest.writeBoolean(mIsAutoJoinEnabled);
     }
 
     @Override
@@ -523,6 +561,7 @@
                 && mUsageLimitDataLimit == that.mUsageLimitDataLimit
                 && mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes
                 && mCarrierId == that.mCarrierId
+                && mIsAutoJoinEnabled == that.mIsAutoJoinEnabled
                 && (mServiceFriendlyNames == null ? that.mServiceFriendlyNames == null
                 : mServiceFriendlyNames.equals(that.mServiceFriendlyNames));
     }
@@ -533,7 +572,7 @@
                 mUpdateIdentifier, mCredentialPriority, mSubscriptionCreationTimeInMillis,
                 mSubscriptionExpirationTimeInMillis, mUsageLimitUsageTimePeriodInMinutes,
                 mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes,
-                mServiceFriendlyNames, mCarrierId);
+                mServiceFriendlyNames, mCarrierId, mIsAutoJoinEnabled);
     }
 
     @Override
@@ -587,6 +626,7 @@
             builder.append("ServiceFriendlyNames: ").append(mServiceFriendlyNames);
         }
         builder.append("CarrierId:" + mCarrierId);
+        builder.append("IsAutoJoinEnabled:" + mIsAutoJoinEnabled);
         return builder.toString();
     }
 
@@ -692,6 +732,7 @@
                         "serviceFriendlyNames");
                 config.setServiceFriendlyNames(friendlyNamesMap);
                 config.mCarrierId = in.readInt();
+                config.mIsAutoJoinEnabled = in.readBoolean();
                 return config;
             }
 
diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
index d58083c..3c13562d 100644
--- a/wifi/java/com/android/server/wifi/BaseWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -182,6 +182,11 @@
     }
 
     @Override
+    public void allowAutojoinPasspoint(String fqdn, boolean enableAutoJoin) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public boolean startScan(String packageName, String featureId) {
         throw new UnsupportedOperationException();
     }
@@ -584,4 +589,10 @@
     public int calculateSignalLevel(int rssi) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(
+            List<ScanResult> scanResults) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index 1f60103..6884a4e 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -25,8 +25,14 @@
 
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
 @SmallTest
 public class SoftApConfigurationTest {
+    private static final String TEST_CHAR_SET_AS_STRING = "abcdefghijklmnopqrstuvwxyz0123456789";
+
     private SoftApConfiguration parcelUnparcel(SoftApConfiguration configIn) {
         Parcel parcel = Parcel.obtain();
         parcel.writeParcelable(configIn, 0);
@@ -37,6 +43,25 @@
         return configOut;
     }
 
+    /**
+     * Helper method to generate random string.
+     *
+     * Note: this method has limited use as a random string generator.
+     * The characters used in this method do no not cover all valid inputs.
+     * @param length number of characters to generate for the string
+     * @return String generated string of random characters
+     */
+    private String generateRandomString(int length) {
+        Random random = new Random();
+        StringBuilder stringBuilder = new StringBuilder(length);
+        int index = -1;
+        while (stringBuilder.length() < length) {
+            index = random.nextInt(TEST_CHAR_SET_AS_STRING.length());
+            stringBuilder.append(TEST_CHAR_SET_AS_STRING.charAt(index));
+        }
+        return stringBuilder.toString();
+    }
+
     @Test
     public void testBasicSettings() {
         SoftApConfiguration original = new SoftApConfiguration.Builder()
@@ -45,7 +70,7 @@
                 .build();
         assertThat(original.getSsid()).isEqualTo("ssid");
         assertThat(original.getBssid()).isEqualTo(MacAddress.fromString("11:22:33:44:55:66"));
-        assertThat(original.getWpa2Passphrase()).isNull();
+        assertThat(original.getPassphrase()).isNull();
         assertThat(original.getSecurityType()).isEqualTo(SoftApConfiguration.SECURITY_TYPE_OPEN);
         assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_2GHZ);
         assertThat(original.getChannel()).isEqualTo(0);
@@ -66,9 +91,9 @@
     @Test
     public void testWpa2() {
         SoftApConfiguration original = new SoftApConfiguration.Builder()
-                .setWpa2Passphrase("secretsecret")
+                .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                 .build();
-        assertThat(original.getWpa2Passphrase()).isEqualTo("secretsecret");
+        assertThat(original.getPassphrase()).isEqualTo("secretsecret");
         assertThat(original.getSecurityType()).isEqualTo(
                 SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
         assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_2GHZ);
@@ -89,20 +114,30 @@
 
     @Test
     public void testWpa2WithAllFieldCustomized() {
+        List<MacAddress> testBlockedClientList = new ArrayList<>();
+        List<MacAddress> testAllowedClientList = new ArrayList<>();
+        testBlockedClientList.add(MacAddress.fromString("11:22:33:44:55:66"));
+        testAllowedClientList.add(MacAddress.fromString("aa:bb:cc:dd:ee:ff"));
         SoftApConfiguration original = new SoftApConfiguration.Builder()
-                .setWpa2Passphrase("secretsecret")
-                .setBand(SoftApConfiguration.BAND_ANY)
+                .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                 .setChannel(149, SoftApConfiguration.BAND_5GHZ)
                 .setHiddenSsid(true)
                 .setMaxNumberOfClients(10)
+                .setShutdownTimeoutMillis(500000)
+                .enableClientControlByUser(true)
+                .setClientList(testBlockedClientList, testAllowedClientList)
                 .build();
-        assertThat(original.getWpa2Passphrase()).isEqualTo("secretsecret");
+        assertThat(original.getPassphrase()).isEqualTo("secretsecret");
         assertThat(original.getSecurityType()).isEqualTo(
                 SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
         assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ);
         assertThat(original.getChannel()).isEqualTo(149);
         assertThat(original.isHiddenSsid()).isEqualTo(true);
         assertThat(original.getMaxNumberOfClients()).isEqualTo(10);
+        assertThat(original.getShutdownTimeoutMillis()).isEqualTo(500000);
+        assertThat(original.isClientControlByUserEnabled()).isEqualTo(true);
+        assertThat(original.getBlockedClientList()).isEqualTo(testBlockedClientList);
+        assertThat(original.getAllowedClientList()).isEqualTo(testAllowedClientList);
 
         SoftApConfiguration unparceled = parcelUnparcel(original);
         assertThat(unparceled).isNotSameAs(original);
@@ -114,4 +149,117 @@
         assertThat(copy).isEqualTo(original);
         assertThat(copy.hashCode()).isEqualTo(original.hashCode());
     }
+
+    @Test
+    public void testWpa3Sae() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA3_SAE)
+                .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+                .setHiddenSsid(true)
+                .build();
+        assertThat(original.getPassphrase()).isEqualTo("secretsecret");
+        assertThat(original.getSecurityType()).isEqualTo(
+                SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
+        assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ);
+        assertThat(original.getChannel()).isEqualTo(149);
+        assertThat(original.isHiddenSsid()).isEqualTo(true);
+
+
+        SoftApConfiguration unparceled = parcelUnparcel(original);
+        assertThat(unparceled).isNotSameAs(original);
+        assertThat(unparceled).isEqualTo(original);
+        assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
+
+        SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
+        assertThat(copy).isNotSameAs(original);
+        assertThat(copy).isEqualTo(original);
+        assertThat(copy.hashCode()).isEqualTo(original.hashCode());
+    }
+
+    @Test
+    public void testWpa3SaeTransition() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setPassphrase("secretsecret",
+                        SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
+                .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+                .setHiddenSsid(true)
+                .build();
+        assertThat(original.getSecurityType()).isEqualTo(
+                SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION);
+        assertThat(original.getPassphrase()).isEqualTo("secretsecret");
+        assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ);
+        assertThat(original.getChannel()).isEqualTo(149);
+        assertThat(original.isHiddenSsid()).isEqualTo(true);
+
+
+        SoftApConfiguration unparceled = parcelUnparcel(original);
+        assertThat(unparceled).isNotSameAs(original);
+        assertThat(unparceled).isEqualTo(original);
+        assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
+
+        SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
+        assertThat(copy).isNotSameAs(original);
+        assertThat(copy).isEqualTo(original);
+        assertThat(copy.hashCode()).isEqualTo(original.hashCode());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidShortPasswordLengthForWpa2() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MIN_LEN - 1),
+                        SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+                .setHiddenSsid(true)
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidLongPasswordLengthForWpa2() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MAX_LEN + 1),
+                        SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+                .setHiddenSsid(true)
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidShortPasswordLengthForWpa3SaeTransition() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MIN_LEN - 1),
+                        SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
+                .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+                .setHiddenSsid(true)
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidLongPasswordLengthForWpa3SaeTransition() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MAX_LEN + 1),
+                        SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
+                .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+                .setHiddenSsid(true)
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalieShutdownTimeoutMillis() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setShutdownTimeoutMillis(-1)
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testsetClientListExceptionWhenExistMacAddressInBothList() {
+        final MacAddress testMacAddress_1 = MacAddress.fromString("22:33:44:55:66:77");
+        final MacAddress testMacAddress_2 = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
+        ArrayList<MacAddress> testAllowedClientList = new ArrayList<>();
+        testAllowedClientList.add(testMacAddress_1);
+        testAllowedClientList.add(testMacAddress_2);
+        ArrayList<MacAddress> testBlockedClientList = new ArrayList<>();
+        testBlockedClientList.add(testMacAddress_1);
+        SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setClientList(testBlockedClientList, testAllowedClientList);
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 909cfef..8689a38 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -16,6 +16,10 @@
 
 package android.net.wifi;
 
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_OWE;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_SAE;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -328,4 +332,57 @@
             assertNotNull(NetworkSelectionStatus.DISABLE_REASON_INFOS.get(i));
         }
     }
+
+    /**
+     * Ensure that {@link WifiConfiguration#setSecurityParams(int)} sets up the
+     * {@link WifiConfiguration} object correctly for SAE security type.
+     * @throws Exception
+     */
+    @Test
+    public void testSetSecurityParamsForSae() throws Exception {
+        WifiConfiguration config = new WifiConfiguration();
+
+        config.setSecurityParams(SECURITY_TYPE_SAE);
+
+        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE));
+        assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.CCMP));
+        assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.CCMP));
+        assertTrue(config.requirePMF);
+    }
+
+    /**
+     * Ensure that {@link WifiConfiguration#setSecurityParams(int)} sets up the
+     * {@link WifiConfiguration} object correctly for OWE security type.
+     * @throws Exception
+     */
+    @Test
+    public void testSetSecurityParamsForOwe() throws Exception {
+        WifiConfiguration config = new WifiConfiguration();
+
+        config.setSecurityParams(SECURITY_TYPE_OWE);
+
+        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE));
+        assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.CCMP));
+        assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.CCMP));
+        assertTrue(config.requirePMF);
+    }
+
+    /**
+     * Ensure that {@link WifiConfiguration#setSecurityParams(int)} sets up the
+     * {@link WifiConfiguration} object correctly for Suite-B security type.
+     * @throws Exception
+     */
+    @Test
+    public void testSetSecurityParamsForSuiteB() throws Exception {
+        WifiConfiguration config = new WifiConfiguration();
+
+        config.setSecurityParams(SECURITY_TYPE_EAP_SUITE_B);
+
+        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+        assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.GCMP_256));
+        assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.GCMP_256));
+        assertTrue(config.allowedGroupManagementCiphers
+                .get(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256));
+        assertTrue(config.requirePMF);
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index f9bd31d..5bdc344 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -70,6 +70,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.net.DhcpInfo;
+import android.net.MacAddress;
 import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
 import android.net.wifi.WifiManager.LocalOnlyHotspotObserver;
 import android.net.wifi.WifiManager.LocalOnlyHotspotReservation;
@@ -897,6 +898,25 @@
     }
 
     /*
+     * Verify client-provided callback is being called through callback proxy
+     */
+    @Test
+    public void softApCallbackProxyCallsOnBlockedClientConnecting() throws Exception {
+        WifiClient testWifiClient = new WifiClient(MacAddress.fromString("22:33:44:55:66:77"));
+        ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+                ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
+        mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
+        verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
+                anyInt());
+
+        callbackCaptor.getValue().onBlockedClientConnecting(testWifiClient,
+                WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+        mLooper.dispatchAll();
+        verify(mSoftApCallback).onBlockedClientConnecting(testWifiClient,
+                WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+    }
+
+    /*
      * Verify client-provided callback is being called through callback proxy on multiple events
      */
     @Test
@@ -1672,10 +1692,22 @@
     @Test
     public void testAllowAutojoin() throws Exception {
         mWifiManager.allowAutojoin(1, true);
-        verify(mWifiService).allowAutojoin(eq(1), eq(true));
+        verify(mWifiService).allowAutojoin(1, true);
     }
 
     /**
+     * Test behavior of {@link WifiManager#allowAutojoinPasspoint(String, boolean)}
+     * @throws Exception
+     */
+    @Test
+    public void testAllowAutojoinPasspoint() throws Exception {
+        final String fqdn = "FullyQualifiedDomainName";
+        mWifiManager.allowAutojoinPasspoint(fqdn, true);
+        verify(mWifiService).allowAutojoinPasspoint(fqdn, true);
+    }
+
+
+    /**
      * Test behavior of {@link WifiManager#disconnect()}
      */
     @Test
@@ -2173,4 +2205,18 @@
         result = WifiManager.parseDppChannelList(channelList);
         assertEquals(result.size(), 0);
     }
+
+    /**
+     * Test getWifiConfigsForMatchedNetworkSuggestions for given scanResults.
+     */
+    @Test
+    public void testGetWifiConfigsForMatchedNetworkSuggestions() throws Exception {
+        List<WifiConfiguration> testResults = new ArrayList<>();
+        testResults.add(new WifiConfiguration());
+
+        when(mWifiService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(any(List.class)))
+                .thenReturn(testResults);
+        assertEquals(testResults, mWifiManager
+                .getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(new ArrayList<>()));
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 4cdc4bc..ac91544 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -76,7 +76,7 @@
                 .setSsid(TEST_SSID)
                 .setWpa2Passphrase(TEST_PRESHARED_KEY)
                 .setIsAppInteractionRequired(true)
-                .setIsUserAllowedToManuallyConnect(false)
+                .setCredentialSharedWithUser(false)
                 .setPriority(0)
                 .build();
 
@@ -151,7 +151,7 @@
         WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
                 .setSsid(TEST_SSID)
                 .setWpa3Passphrase(TEST_PRESHARED_KEY)
-                .setIsUserAllowedToManuallyConnect(true)
+                .setCredentialSharedWithUser(true)
                 .build();
 
         assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
@@ -709,14 +709,14 @@
 
     /**
      * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
-     * when {@link WifiNetworkSuggestion.Builder#setIsUserAllowedToManuallyConnect(boolean)} to
+     * when {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to
      * true on a open network suggestion.
      */
     @Test(expected = IllegalStateException.class)
     public void testSetIsUserAllowedToManuallyConnectToWithOpenNetwork() {
         WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
                 .setSsid(TEST_SSID)
-                .setIsUserAllowedToManuallyConnect(true)
+                .setCredentialSharedWithUser(true)
                 .build();
     }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
index 1af0bcb..0cc76b6 100644
--- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
@@ -366,7 +366,7 @@
 
     /**
      * Test behavior of {@link WifiScanner#startDisconnectedPnoScan(ScanSettings, PnoSettings,
-     * WifiScanner.PnoScanListener)}
+     * Executor, WifiScanner.PnoScanListener)}
      * @throws Exception
      */
     @Test
@@ -375,7 +375,8 @@
         PnoSettings pnoSettings = new PnoSettings();
         WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class);
 
-        mWifiScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, pnoScanListener);
+        mWifiScanner.startDisconnectedPnoScan(
+                scanSettings, pnoSettings, mock(Executor.class), pnoScanListener);
         mLooper.dispatchAll();
 
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -396,7 +397,7 @@
 
     /**
      * Test behavior of {@link WifiScanner#startConnectedPnoScan(ScanSettings, PnoSettings,
-     * WifiScanner.PnoScanListener)}
+     * Executor, WifiScanner.PnoScanListener)}
      * @throws Exception
      */
     @Test
@@ -405,7 +406,8 @@
         PnoSettings pnoSettings = new PnoSettings();
         WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class);
 
-        mWifiScanner.startConnectedPnoScan(scanSettings, pnoSettings, pnoScanListener);
+        mWifiScanner.startConnectedPnoScan(
+                scanSettings, pnoSettings, mock(Executor.class), pnoScanListener);
         mLooper.dispatchAll();
 
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -426,7 +428,7 @@
 
     /**
      * Test behavior of {@link WifiScanner#stopPnoScan(ScanListener)}
-     * WifiScanner.PnoScanListener)}
+     * Executor, WifiScanner.PnoScanListener)}
      * @throws Exception
      */
     @Test
@@ -435,7 +437,8 @@
         PnoSettings pnoSettings = new PnoSettings();
         WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class);
 
-        mWifiScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, pnoScanListener);
+        mWifiScanner.startDisconnectedPnoScan(
+                scanSettings, pnoSettings, mock(Executor.class), pnoScanListener);
         mLooper.dispatchAll();
         mWifiScanner.stopPnoScan(pnoScanListener);
         mLooper.dispatchAll();
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index f501b16..94054fd 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -171,6 +171,7 @@
 
         assertFalse(config.validate());
         assertFalse(config.validateForR2());
+        assertTrue(config.isAutoJoinEnabled());
     }
 
     /**