Merge changes from topics "wifi-copy-xmlutils", "wifi-systemmessageproto"

* changes:
  wifi-service: Remove jarjar of XmlUtils
  Statically link SystemMessageProto into wifi-service
diff --git a/Android.bp b/Android.bp
index ff6ecb0..908274d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -486,7 +486,7 @@
         "framework-minus-apex",
         "updatable_media_stubs",
         "framework_mediaprovider_stubs",
-        "framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs
+        "framework-appsearch-stubs",
         "framework-sdkextensions-stubs-systemapi",
         // TODO(b/146167933): Use framework-statsd-stubs instead.
         "framework-statsd",
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 1f30dda..60cc3be 100644
--- a/apex/appsearch/framework/Android.bp
+++ b/apex/appsearch/framework/Android.bp
@@ -30,29 +30,10 @@
   libs: [
     "framework-minus-apex",  // TODO(b/146218515) should be framework-system-stubs
   ],
-  visibility: [
-      "//frameworks/base/apex/appsearch:__subpackages__",
-      // TODO(b/146218515) remove this when framework is built with the stub of appsearch
-      "//frameworks/base",
-  ],
+  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: [
@@ -62,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 {
@@ -75,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/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/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/api/current.txt b/api/current.txt
index 8c751b1..00a5815 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2925,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 {
@@ -6763,6 +6764,7 @@
     method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String);
     method public CharSequence getDeviceOwnerLockScreenInfo();
     method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName);
+    method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName);
     method @Nullable public String getGlobalPrivateDnsHost(@NonNull android.content.ComponentName);
     method public int getGlobalPrivateDnsMode(@NonNull android.content.ComponentName);
     method @NonNull public java.util.List<byte[]> getInstalledCaCerts(@Nullable android.content.ComponentName);
@@ -6881,6 +6883,7 @@
     method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>);
     method public void setDeviceOwnerLockScreenInfo(@NonNull android.content.ComponentName, CharSequence);
     method public void setEndUserSessionMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
+    method public void setFactoryResetProtectionPolicy(@NonNull android.content.ComponentName, @Nullable android.app.admin.FactoryResetProtectionPolicy);
     method public int setGlobalPrivateDnsModeOpportunistic(@NonNull android.content.ComponentName);
     method @WorkerThread public int setGlobalPrivateDnsModeSpecifiedHost(@NonNull android.content.ComponentName, @NonNull String);
     method public void setGlobalSetting(@NonNull android.content.ComponentName, String, String);
@@ -7115,6 +7118,21 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DnsEvent> CREATOR;
   }
 
+  public final class FactoryResetProtectionPolicy implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<java.lang.String> getFactoryResetProtectionAccounts();
+    method public boolean isFactoryResetProtectionDisabled();
+    method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FactoryResetProtectionPolicy> CREATOR;
+  }
+
+  public static class FactoryResetProtectionPolicy.Builder {
+    ctor public FactoryResetProtectionPolicy.Builder();
+    method @NonNull public android.app.admin.FactoryResetProtectionPolicy build();
+    method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionAccounts(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionDisabled(boolean);
+  }
+
   public class FreezePeriod {
     ctor public FreezePeriod(java.time.MonthDay, java.time.MonthDay);
     method public java.time.MonthDay getEnd();
@@ -10008,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
@@ -17061,13 +17080,13 @@
     method public abstract void close();
     method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException;
     method @NonNull public android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int, java.util.Set<java.lang.String>) throws android.hardware.camera2.CameraAccessException;
-    method public abstract void createCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method @Deprecated public abstract void createCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public void createCaptureSession(android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
-    method public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
-    method public abstract void createConstrainedHighSpeedCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method @Deprecated public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method @Deprecated public abstract void createConstrainedHighSpeedCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(@NonNull android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException;
-    method public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
-    method public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method @Deprecated public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method @Deprecated public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method public int getCameraAudioRestriction() throws android.hardware.camera2.CameraAccessException;
     method @NonNull public abstract String getId();
     method public boolean isSessionConfigurationSupported(@NonNull android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
@@ -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);
@@ -28702,7 +28804,7 @@
     method public boolean isEncrypted();
     method public boolean isHardOfHearing();
     method public boolean isSpokenSubtitle();
-    method public void writeToParcel(android.os.Parcel, int);
+    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
@@ -28711,21 +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 @NonNull public android.media.tv.TvTrackInfo.Builder setExtra(@NonNull android.os.Bundle);
     method @NonNull public android.media.tv.TvTrackInfo.Builder setHardOfHearing(boolean);
-    method public android.media.tv.TvTrackInfo.Builder setLanguage(String);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setLanguage(@NonNull String);
     method @NonNull public android.media.tv.TvTrackInfo.Builder setSpokenSubtitle(boolean);
-    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 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 {
@@ -30542,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";
@@ -30550,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";
@@ -42687,14 +42791,19 @@
     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
@@ -45053,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";
@@ -45544,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);
@@ -45781,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);
@@ -45791,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);
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 1c5d763b..8e3b11d 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -190,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";
@@ -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 {
@@ -812,7 +814,9 @@
     field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
     field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
     field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION";
+    field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
     field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+    field public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
     field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
     field public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
     field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
@@ -1329,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();
@@ -2407,7 +2415,7 @@
 package android.hardware.camera2 {
 
   public abstract class CameraDevice implements java.lang.AutoCloseable {
-    method public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method @Deprecated public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     field public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // 0x1
     field public static final int SESSION_OPERATION_MODE_NORMAL = 0; // 0x0
     field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000
@@ -3566,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;
@@ -4362,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 {
@@ -4611,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();
@@ -4633,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
   }
 
 }
@@ -5847,7 +5889,9 @@
 
   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();
@@ -5855,6 +5899,7 @@
     method public int getSecurityType();
     method public int getShutdownTimeoutMillis();
     method @Nullable public String getSsid();
+    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
@@ -5872,9 +5917,11 @@
     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);
@@ -6113,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
@@ -6154,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);
@@ -8061,7 +8111,6 @@
     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";
@@ -10510,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";
diff --git a/api/test-current.txt b/api/test-current.txt
index a8f7b51..fd692d6 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
   }
 
 }
@@ -1020,7 +1021,7 @@
 package android.hardware.camera2 {
 
   public abstract class CameraDevice implements java.lang.AutoCloseable {
-    method public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+    method @Deprecated public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     field public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // 0x1
     field public static final int SESSION_OPERATION_MODE_NORMAL = 0; // 0x0
     field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000
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/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 2bacfbc..fb43783 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -4725,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;
@@ -4779,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;
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/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/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 2aac94c..69640b8 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -398,6 +398,42 @@
             "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
 
     /**
+     * Activity action: Starts the provisioning flow which sets up a financed device.
+     *
+     * <p>During financed device provisioning, a device admin app is downloaded and set as the owner
+     * of the device. A device owner has full control over the device. The device owner can not be
+     * modified by the user.
+     *
+     * <p>A typical use case would be a device that is bought from the reseller through financing
+     * program.
+     *
+     * <p>An intent with this action can be sent only on an unprovisioned device.
+     *
+     * <p>Unlike {@link #ACTION_PROVISION_MANAGED_DEVICE}, the provisioning message can only be sent
+     * by a privileged app with the permission
+     * {@link android.Manifest.permission#DISPATCH_PROVISIONING_MESSAGE}.
+     *
+     * <p>The provisioning intent contains the following properties:
+     * <ul>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_SUPPORT_URL}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_ORGANIZATION_NAME}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
+     * </ul>
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_PROVISION_FINANCED_DEVICE =
+            "android.app.action.PROVISION_FINANCED_DEVICE";
+
+    /**
      * Activity action: Starts the provisioning flow which sets up a managed device.
      * Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}.
      *
@@ -864,6 +900,7 @@
      * The name is displayed only during provisioning.
      *
      * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+     * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
      *
      * @hide
      */
@@ -876,6 +913,7 @@
      * during provisioning. If the url is not HTTPS, an error will be shown.
      *
      * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+     * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
      *
      * @hide
      */
@@ -888,6 +926,7 @@
      * as the app label of the package.
      *
      * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+     * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
      *
      * @hide
      */
@@ -912,6 +951,7 @@
      * {@link android.content.ClipData} of the intent too.
      *
      * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+     * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
      *
      * @hide
      */
@@ -1371,6 +1411,16 @@
             = "android.app.action.DEVICE_OWNER_CHANGED";
 
     /**
+     * Broadcast action: sent when the factory reset protection (FRP) policy is changed.
+     *
+     * @see #setFactoryResetProtectionPolicy
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED =
+            "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
+
+    /**
      * The ComponentName of the administrator component.
      *
      * @see #ACTION_ADD_DEVICE_ADMIN
@@ -4289,6 +4339,60 @@
     }
 
     /**
+     * Callable by device owner or profile owner of an organization-owned device, to set a
+     * factory reset protection (FRP) policy. When a new policy is set, the system
+     * notifies the FRP management agent of a policy change by broadcasting
+     * {@code ACTION_RESET_PROTECTION_POLICY_CHANGED}.
+     *
+     * @param admin  Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param policy the new FRP policy, or {@code null} to clear the current policy.
+     * @throws SecurityException if {@code admin} is not a device owner or a profile owner of
+     *                           an organization-owned device.
+     * @throws UnsupportedOperationException if factory reset protection is not
+     *                           supported on the device.
+     */
+    public void setFactoryResetProtectionPolicy(@NonNull ComponentName admin,
+            @Nullable FactoryResetProtectionPolicy policy) {
+        throwIfParentInstance("setFactoryResetProtectionPolicy");
+        if (mService != null) {
+            try {
+                mService.setFactoryResetProtectionPolicy(admin, policy);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Callable by device owner or profile owner of an organization-owned device, to retrieve
+     * the current factory reset protection (FRP) policy set previously by
+     * {@link #setFactoryResetProtectionPolicy}.
+     * <p>
+     * This method can also be called by the FRP management agent on device, in which case,
+     * it can pass {@code null} as the ComponentName.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with or
+     *              {@code null} if called by the FRP management agent on device.
+     * @return The current FRP policy object or {@code null} if no policy is set.
+     * @throws SecurityException if {@code admin} is not a device owner, a profile owner of
+     *                           an organization-owned device or the FRP management agent.
+     * @throws UnsupportedOperationException if factory reset protection is not
+     *                           supported on the device.
+     */
+    public @Nullable FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(
+            @Nullable ComponentName admin) {
+        throwIfParentInstance("getFactoryResetProtectionPolicy");
+        if (mService != null) {
+            try {
+                return mService.getFactoryResetProtectionPolicy(admin);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return null;
+    }
+
+    /**
      * Called by an application that is administering the device to set the
      * global proxy and exclusion list.
      * <p>
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl
similarity index 81%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to core/java/android/app/admin/FactoryResetProtectionPolicy.aidl
index fb5d836..72e639a 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 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.media;
+package android.app.admin;
 
-parcelable RouteSessionInfo;
+parcelable FactoryResetProtectionPolicy;
diff --git a/core/java/android/app/admin/FactoryResetProtectionPolicy.java b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
new file mode 100644
index 0000000..ed74779
--- /dev/null
+++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.TEXT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The factory reset protection policy determines which accounts can unlock a device that
+ * has gone through untrusted factory reset.
+ * <p>
+ * Only a device owner or profile owner of an organization-owned device can set a factory
+ * reset protection policy for the device by calling the {@code DevicePolicyManager} method
+ * {@link DevicePolicyManager#setFactoryResetProtectionPolicy(ComponentName,
+ * FactoryResetProtectionPolicy)}}.
+ *
+ * @see DevicePolicyManager#setFactoryResetProtectionPolicy
+ * @see DevicePolicyManager#getFactoryResetProtectionPolicy
+ */
+public final class FactoryResetProtectionPolicy implements Parcelable {
+
+    private static final String LOG_TAG = "FactoryResetProtectionPolicy";
+
+    private static final String KEY_FACTORY_RESET_PROTECTION_ACCOUNT =
+            "factory_reset_protection_account";
+    private static final String KEY_FACTORY_RESET_PROTECTION_DISABLED =
+            "factory_reset_protection_disabled";
+    private static final String ATTR_VALUE = "value";
+
+    private final List<String> mFactoryResetProtectionAccounts;
+    private final boolean mFactoryResetProtectionDisabled;
+
+    private FactoryResetProtectionPolicy(List<String> factoryResetProtectionAccounts,
+            boolean factoryResetProtectionDisabled) {
+        mFactoryResetProtectionAccounts = factoryResetProtectionAccounts;
+        mFactoryResetProtectionDisabled = factoryResetProtectionDisabled;
+    }
+
+    /**
+     * Get the list of accounts that can provision a device which has been factory reset.
+     */
+    public @NonNull List<String> getFactoryResetProtectionAccounts() {
+        return mFactoryResetProtectionAccounts;
+    }
+
+    /**
+     * Return whether factory reset protection for the device is disabled or not.
+     */
+    public boolean isFactoryResetProtectionDisabled() {
+        return mFactoryResetProtectionDisabled;
+    }
+
+    /**
+     * Builder class for {@link FactoryResetProtectionPolicy} objects.
+     */
+    public static class Builder {
+        private List<String> mFactoryResetProtectionAccounts;
+        private boolean mFactoryResetProtectionDisabled;
+
+        /**
+         * Initialize a new Builder to construct a {@link FactoryResetProtectionPolicy}.
+         */
+        public Builder() {
+        };
+
+        /**
+         * Sets which accounts can unlock a device that has been factory reset.
+         * <p>
+         * Once set, the consumer unlock flow will be disabled and only accounts in this list
+         * can unlock factory reset protection after untrusted factory reset.
+         * <p>
+         * It's up to the FRP management agent to interpret the {@code String} as account it
+         * supports. Please consult their relevant documentation for details.
+         *
+         * @param factoryResetProtectionAccounts list of accounts.
+         * @return the same Builder instance.
+         */
+        @NonNull
+        public Builder setFactoryResetProtectionAccounts(
+                @NonNull List<String> factoryResetProtectionAccounts) {
+            mFactoryResetProtectionAccounts = new ArrayList<>(factoryResetProtectionAccounts);
+            return this;
+        }
+
+        /**
+         * Sets whether factory reset protection is disabled or not.
+         * <p>
+         * Once disabled, factory reset protection will not kick in all together when the device
+         * goes through untrusted factory reset. This applies to both the consumer unlock flow and
+         * the admin account overrides via {@link #setFactoryResetProtectionAccounts}
+         *
+         * @param factoryResetProtectionDisabled Whether the policy is disabled or not.
+         * @return the same Builder instance.
+         */
+        @NonNull
+        public Builder setFactoryResetProtectionDisabled(boolean factoryResetProtectionDisabled) {
+            mFactoryResetProtectionDisabled = factoryResetProtectionDisabled;
+            return this;
+        }
+
+        /**
+         * Combines all of the attributes that have been set on this {@code Builder}
+         *
+         * @return a new {@link FactoryResetProtectionPolicy} object.
+         */
+        @NonNull
+        public FactoryResetProtectionPolicy build() {
+            return new FactoryResetProtectionPolicy(mFactoryResetProtectionAccounts,
+                    mFactoryResetProtectionDisabled);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "FactoryResetProtectionPolicy{"
+                + "mFactoryResetProtectionAccounts=" + mFactoryResetProtectionAccounts
+                + ", mFactoryResetProtectionDisabled=" + mFactoryResetProtectionDisabled
+                + '}';
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, @Nullable int flags) {
+        int accountsCount = mFactoryResetProtectionAccounts.size();
+        dest.writeInt(accountsCount);
+        for (String account: mFactoryResetProtectionAccounts) {
+            dest.writeString(account);
+        }
+        dest.writeBoolean(mFactoryResetProtectionDisabled);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<FactoryResetProtectionPolicy> CREATOR =
+            new Creator<FactoryResetProtectionPolicy>() {
+
+                @Override
+                public FactoryResetProtectionPolicy createFromParcel(Parcel in) {
+                    List<String> factoryResetProtectionAccounts = new ArrayList<>();
+                    int accountsCount = in.readInt();
+                    for (int i = 0; i < accountsCount; i++) {
+                        factoryResetProtectionAccounts.add(in.readString());
+                    }
+                    boolean factoryResetProtectionDisabled = in.readBoolean();
+
+                    return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts,
+                            factoryResetProtectionDisabled);
+                }
+
+                @Override
+                public FactoryResetProtectionPolicy[] newArray(int size) {
+                    return new FactoryResetProtectionPolicy[size];
+                }
+    };
+
+    /**
+     * Restore a previously saved FactoryResetProtectionPolicy from XML.
+     * <p>
+     * No validation is required on the reconstructed policy since the XML was previously
+     * created by the system server from a validated policy.
+     * @hide
+     */
+    @Nullable
+    public static FactoryResetProtectionPolicy readFromXml(@NonNull XmlPullParser parser) {
+        try {
+            boolean factoryResetProtectionDisabled = Boolean.parseBoolean(
+                    parser.getAttributeValue(null, KEY_FACTORY_RESET_PROTECTION_DISABLED));
+
+            List<String> factoryResetProtectionAccounts = new ArrayList<>();
+            int outerDepth = parser.getDepth();
+            int type;
+            while ((type = parser.next()) != END_DOCUMENT
+                    && (type != END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == END_TAG || type == TEXT) {
+                    continue;
+                }
+                if (!parser.getName().equals(KEY_FACTORY_RESET_PROTECTION_ACCOUNT)) {
+                    continue;
+                }
+                factoryResetProtectionAccounts.add(
+                        parser.getAttributeValue(null, ATTR_VALUE));
+            }
+
+            return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts,
+                    factoryResetProtectionDisabled);
+        } catch (XmlPullParserException | IOException e) {
+            Log.w(LOG_TAG, "Reading from xml failed", e);
+        }
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    public void writeToXml(@NonNull XmlSerializer out) throws IOException {
+        out.attribute(null, KEY_FACTORY_RESET_PROTECTION_DISABLED,
+                Boolean.toString(mFactoryResetProtectionDisabled));
+        for (String account : mFactoryResetProtectionAccounts) {
+            out.startTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT);
+            out.attribute(null, ATTR_VALUE, account);
+            out.endTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT);
+        }
+    }
+
+}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3eec46b..21c9eb5 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -24,6 +24,7 @@
 import android.app.admin.SystemUpdateInfo;
 import android.app.admin.SystemUpdatePolicy;
 import android.app.admin.PasswordMetrics;
+import android.app.admin.FactoryResetProtectionPolicy;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -104,6 +105,9 @@
 
     void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent);
 
+    void setFactoryResetProtectionPolicy(in ComponentName who, in FactoryResetProtectionPolicy policy);
+    FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(in ComponentName who);
+
     ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
     ComponentName getGlobalProxyAdmin(int userHandle);
     void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo);
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/content/Context.java b/core/java/android/content/Context.java
index 44b2df6..1b40a18 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3416,6 +3416,7 @@
             TELEPHONY_SUBSCRIPTION_SERVICE,
             CARRIER_CONFIG_SERVICE,
             EUICC_SERVICE,
+            MMS_SERVICE,
             TELECOM_SERVICE,
             CLIPBOARD_SERVICE,
             INPUT_METHOD_SERVICE,
@@ -3612,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
@@ -4288,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/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/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/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java b/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java
index 07d2443..6ead64c 100644
--- a/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java
@@ -25,9 +25,12 @@
  * A constrained high speed capture session for a {@link CameraDevice}, used for capturing high
  * speed images from the {@link CameraDevice} for high speed video recording use case.
  * <p>
- * A CameraHighSpeedCaptureSession is created by providing a set of target output surfaces to
- * {@link CameraDevice#createConstrainedHighSpeedCaptureSession}, Once created, the session is
- * active until a new session is created by the camera device, or the camera device is closed.
+ * A CameraConstrainedHighSpeedCaptureSession is created by providing a session configuration to
+ * {@link CameraDevice#createCaptureSession(SessionConfiguration)} with a type of
+ * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_HIGH_SPEED}. The
+ * CameraCaptureSession returned from {@link CameraCaptureSession.StateCallback} can then be cast to
+ * a CameraConstrainedHighSpeedCaptureSession. Once created, the session is active until a new
+ * session is created by the camera device, or the camera device is closed.
  * </p>
  * <p>
  * An active high speed capture session is a specialized capture session that is only targeted at
@@ -37,8 +40,8 @@
  * accepts request lists created via {@link #createHighSpeedRequestList}, and the request list can
  * only be submitted to this session via {@link CameraCaptureSession#captureBurst captureBurst}, or
  * {@link CameraCaptureSession#setRepeatingBurst setRepeatingBurst}. See
- * {@link CameraDevice#createConstrainedHighSpeedCaptureSession} for more details of the
- * limitations.
+ * {@link CameraDevice#createCaptureSession(android.hardware.camera2.params.SessionConfiguration)}
+ * for more details of the limitations.
  * </p>
  * <p>
  * Creating a session is an expensive operation and can take several hundred milliseconds, since it
@@ -50,13 +53,6 @@
  * completed, then the {@link CameraCaptureSession.StateCallback#onConfigureFailed} is called, and
  * the session will not become active.
  * </p>
- * <!--
- * <p>
- * Any capture requests (repeating or non-repeating) submitted before the session is ready will be
- * queued up and will begin capture once the session becomes ready. In case the session cannot be
- * configured and {@link CameraCaptureSession.StateCallback#onConfigureFailed onConfigureFailed} is
- * called, all queued capture requests are discarded.  </p>
- * -->
  * <p>
  * If a new session is created by the camera device, then the previous session is closed, and its
  * associated {@link CameraCaptureSession.StateCallback#onClosed onClosed} callback will be
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index fb1ece2..cc06681 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -16,24 +16,22 @@
 
 package android.hardware.camera2;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.IntDef;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import static android.hardware.camera2.ICameraDeviceUser.NORMAL_MODE;
-import static android.hardware.camera2.ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;
 import android.hardware.camera2.params.InputConfiguration;
-import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.SessionConfiguration;
+import android.hardware.camera2.params.StreamConfigurationMap;
 import android.os.Handler;
 import android.view.Surface;
 
-import java.util.List;
-import java.util.Set;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Set;
 
 /**
  * <p>The CameraDevice class is a representation of a single camera connected to an
@@ -220,6 +218,224 @@
      * <p>Create a new camera capture session by providing the target output set of Surfaces to the
      * camera device.</p>
      *
+     * @param outputs The new set of Surfaces that should be made available as
+     *                targets for captured image data.
+     * @param callback The callback to notify about the status of the new capture session.
+     * @param handler The handler on which the callback should be invoked, or {@code null} to use
+     *                the current thread's {@link android.os.Looper looper}.
+     *
+     * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
+     *                                  the callback is null, or the handler is null but the current
+     *                                  thread has no looper.
+     * @throws CameraAccessException if the camera device is no longer connected or has
+     *                               encountered a fatal error
+     * @throws IllegalStateException if the camera device has been closed
+     *
+     * @see CameraCaptureSession
+     * @see StreamConfigurationMap#getOutputFormats()
+     * @see StreamConfigurationMap#getOutputSizes(int)
+     * @see StreamConfigurationMap#getOutputSizes(Class)
+     * @deprecated Please use @{link
+     *      #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+     *      full set of configuration options available.
+     */
+    @Deprecated
+    public abstract void createCaptureSession(@NonNull List<Surface> outputs,
+            @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
+            throws CameraAccessException;
+
+    /**
+     * <p>Create a new camera capture session by providing the target output set of Surfaces and
+     * its corresponding surface configuration to the camera device.</p>
+     *
+     * @see #createCaptureSession
+     * @see OutputConfiguration
+     * @deprecated Please use @{link
+     *      #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+     *      full set of configuration options available.
+     */
+    @Deprecated
+    public abstract void createCaptureSessionByOutputConfigurations(
+            List<OutputConfiguration> outputConfigurations,
+            CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
+            throws CameraAccessException;
+    /**
+     * Create a new reprocessable camera capture session by providing the desired reprocessing
+     * input Surface configuration and the target output set of Surfaces to the camera device.
+     *
+     * @param inputConfig The configuration for the input {@link Surface}
+     * @param outputs The new set of Surfaces that should be made available as
+     *                targets for captured image data.
+     * @param callback The callback to notify about the status of the new capture session.
+     * @param handler The handler on which the callback should be invoked, or {@code null} to use
+     *                the current thread's {@link android.os.Looper looper}.
+     *
+     * @throws IllegalArgumentException if the input configuration is null or not supported, the set
+     *                                  of output Surfaces do not meet the requirements, the
+     *                                  callback is null, or the handler is null but the current
+     *                                  thread has no looper.
+     * @throws CameraAccessException if the camera device is no longer connected or has
+     *                               encountered a fatal error
+     * @throws IllegalStateException if the camera device has been closed
+     *
+     * @see #createCaptureSession
+     * @see CameraCaptureSession
+     * @see StreamConfigurationMap#getInputFormats
+     * @see StreamConfigurationMap#getInputSizes
+     * @see StreamConfigurationMap#getValidOutputFormatsForInput
+     * @see StreamConfigurationMap#getOutputSizes
+     * @see android.media.ImageWriter
+     * @see android.media.ImageReader
+     * @deprecated Please use @{link
+     *      #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+     *      full set of configuration options available.
+     */
+    @Deprecated
+    public abstract void createReprocessableCaptureSession(@NonNull InputConfiguration inputConfig,
+            @NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback,
+            @Nullable Handler handler)
+            throws CameraAccessException;
+
+    /**
+     * Create a new reprocessable camera capture session by providing the desired reprocessing
+     * input configuration and output {@link OutputConfiguration}
+     * to the camera device.
+     *
+     * @see #createReprocessableCaptureSession
+     * @see OutputConfiguration
+     * @deprecated Please use @{link
+     *      #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+     *      full set of configuration options available.
+     */
+    @Deprecated
+    public abstract void createReprocessableCaptureSessionByConfigurations(
+            @NonNull InputConfiguration inputConfig,
+            @NonNull List<OutputConfiguration> outputs,
+            @NonNull CameraCaptureSession.StateCallback callback,
+            @Nullable Handler handler)
+            throws CameraAccessException;
+
+    /**
+     * <p>Create a new constrained high speed capture session.</p>
+     *
+     * @param outputs The new set of Surfaces that should be made available as
+     *                targets for captured high speed image data.
+     * @param callback The callback to notify about the status of the new capture session.
+     * @param handler The handler on which the callback should be invoked, or {@code null} to use
+     *                the current thread's {@link android.os.Looper looper}.
+     *
+     * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
+     *                                  the callback is null, or the handler is null but the current
+     *                                  thread has no looper, or the camera device doesn't support
+     *                                  high speed video capability.
+     * @throws CameraAccessException if the camera device is no longer connected or has
+     *                               encountered a fatal error
+     * @throws IllegalStateException if the camera device has been closed
+     *
+     * @see #createCaptureSession
+     * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE
+     * @see StreamConfigurationMap#getHighSpeedVideoSizes
+     * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor
+     * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+     * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
+     * @see CameraCaptureSession#captureBurst
+     * @see CameraCaptureSession#setRepeatingBurst
+     * @see CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList
+     * @deprecated Please use @{link
+     *      #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+     *      full set of configuration options available.
+     */
+    @Deprecated
+    public abstract void createConstrainedHighSpeedCaptureSession(@NonNull List<Surface> outputs,
+            @NonNull CameraCaptureSession.StateCallback callback,
+            @Nullable Handler handler)
+            throws CameraAccessException;
+
+    /**
+     * Standard camera operation mode.
+     *
+     * @see #createCustomCaptureSession
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int SESSION_OPERATION_MODE_NORMAL =
+            0; // ICameraDeviceUser.NORMAL_MODE;
+
+    /**
+     * Constrained high-speed operation mode.
+     *
+     * @see #createCustomCaptureSession
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED =
+            1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;
+
+    /**
+     * First vendor-specific operating mode
+     *
+     * @see #createCustomCaptureSession
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static final int SESSION_OPERATION_MODE_VENDOR_START =
+            0x8000; // ICameraDeviceUser.VENDOR_MODE_START;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"SESSION_OPERATION_MODE"}, value =
+            {SESSION_OPERATION_MODE_NORMAL,
+             SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED,
+             SESSION_OPERATION_MODE_VENDOR_START})
+    public @interface SessionOperatingMode {};
+
+    /**
+     * Create a new camera capture session with a custom operating mode.
+     *
+     * @param inputConfig The configuration for the input {@link Surface} if a reprocessing session
+     *                is desired, or {@code null} otherwise.
+     * @param outputs The new set of {@link OutputConfiguration OutputConfigurations} that should be
+     *                made available as targets for captured image data.
+     * @param operatingMode The custom operating mode to use; a nonnegative value, either a custom
+     *                vendor value or one of the SESSION_OPERATION_MODE_* values.
+     * @param callback The callback to notify about the status of the new capture session.
+     * @param handler The handler on which the callback should be invoked, or {@code null} to use
+     *                the current thread's {@link android.os.Looper looper}.
+     *
+     * @throws IllegalArgumentException if the input configuration is null or not supported, the set
+     *                                  of output Surfaces do not meet the requirements, the
+     *                                  callback is null, or the handler is null but the current
+     *                                  thread has no looper.
+     * @throws CameraAccessException if the camera device is no longer connected or has
+     *                               encountered a fatal error
+     * @throws IllegalStateException if the camera device has been closed
+     *
+     * @see #createCaptureSession
+     * @see #createReprocessableCaptureSession
+     * @see CameraCaptureSession
+     * @see OutputConfiguration
+     * @deprecated Please use @{link
+     *      #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+     *      full set of configuration options available.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @Deprecated
+    public abstract void createCustomCaptureSession(
+            InputConfiguration inputConfig,
+            @NonNull List<OutputConfiguration> outputs,
+            @SessionOperatingMode int operatingMode,
+            @NonNull CameraCaptureSession.StateCallback callback,
+            @Nullable Handler handler)
+            throws CameraAccessException;
+
+    /**
+     * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper
+     * object that aggregates all supported parameters.</p>
      * <p>The active capture session determines the set of potential output Surfaces for
      * the camera device for each capture request. A given request may use all
      * or only some of the outputs. Once the CameraCaptureSession is created, requests can be
@@ -308,11 +524,15 @@
      * <p>Configuring a session with an empty or null list will close the current session, if
      * any. This can be used to release the current session's target surfaces for another use.</p>
      *
+     * <h3>Regular capture</h3>
+     *
      * <p>While any of the sizes from {@link StreamConfigurationMap#getOutputSizes} can be used when
      * a single output stream is configured, a given camera device may not be able to support all
      * combination of sizes, formats, and targets when multiple outputs are configured at once.  The
      * tables below list the maximum guaranteed resolutions for combinations of streams and targets,
-     * given the capabilities of the camera device.</p>
+     * given the capabilities of the camera device. These are valid for when the
+     * {@link android.hardware.camera2.params.SessionConfiguration#setInputConfiguration
+     * input configuration} is not set and therefore no reprocessing is active.</p>
      *
      * <p>If an application tries to create a session using a set of targets that exceed the limits
      * described in the below tables, one of three possibilities may occur. First, the session may
@@ -488,56 +708,22 @@
      * (either width or height) might not be supported, and capture session creation will fail if it
      * is not.</p>
      *
-     * @param outputs The new set of Surfaces that should be made available as
-     *                targets for captured image data.
-     * @param callback The callback to notify about the status of the new capture session.
-     * @param handler The handler on which the callback should be invoked, or {@code null} to use
-     *                the current thread's {@link android.os.Looper looper}.
-     *
-     * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
-     *                                  the callback is null, or the handler is null but the current
-     *                                  thread has no looper.
-     * @throws CameraAccessException if the camera device is no longer connected or has
-     *                               encountered a fatal error
-     * @throws IllegalStateException if the camera device has been closed
-     *
-     * @see CameraCaptureSession
-     * @see StreamConfigurationMap#getOutputFormats()
-     * @see StreamConfigurationMap#getOutputSizes(int)
-     * @see StreamConfigurationMap#getOutputSizes(Class)
-     */
-    public abstract void createCaptureSession(@NonNull List<Surface> outputs,
-            @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
-            throws CameraAccessException;
-
-    /**
-     * <p>Create a new camera capture session by providing the target output set of Surfaces and
-     * its corresponding surface configuration to the camera device.</p>
-     *
-     * @see #createCaptureSession
-     * @see OutputConfiguration
-     */
-    public abstract void createCaptureSessionByOutputConfigurations(
-            List<OutputConfiguration> outputConfigurations,
-            CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
-            throws CameraAccessException;
-    /**
-     * Create a new reprocessable camera capture session by providing the desired reprocessing
-     * input Surface configuration and the target output set of Surfaces to the camera device.
+     * <h3>Reprocessing</h3>
      *
      * <p>If a camera device supports YUV reprocessing
      * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING}) or PRIVATE
      * reprocessing
-     * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING}), besides
-     * the capture session created via {@link #createCaptureSession createCaptureSession}, the
+     * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING}), the
      * application can also create a reprocessable capture session to submit reprocess capture
-     * requests in addition to regular capture requests. A reprocess capture request takes the next
-     * available buffer from the session's input Surface, and sends it through the camera device's
-     * processing pipeline again, to produce buffers for the request's target output Surfaces. No
-     * new image data is captured for a reprocess request. However the input buffer provided by
-     * the application must be captured previously by the same camera device in the same session
-     * directly (e.g. for Zero-Shutter-Lag use case) or indirectly (e.g. combining multiple output
-     * images).</p>
+     * requests in addition to regular capture requests, by setting an
+     * {@link android.hardware.camera2.params.SessionConfiguration#setInputConfiguration
+     * input configuration} for the session. A reprocess capture request takes the next available
+     * buffer from the
+     * session's input Surface, and sends it through the camera device's processing pipeline again,
+     * to produce buffers for the request's target output Surfaces. No new image data is captured
+     * for a reprocess request. However the input buffer provided by the application must be
+     * captured previously by the same camera device in the same session directly (e.g. for
+     * Zero-Shutter-Lag use case) or indirectly (e.g. combining multiple output images).</p>
      *
      * <p>The active reprocessable capture session determines an input {@link Surface} and the set
      * of potential output Surfaces for the camera devices for each capture request. The application
@@ -570,10 +756,7 @@
      * <p>Starting from API level 30, recreating a reprocessable capture session will flush all the
      * queued but not yet processed buffers from the input surface.</p>
      *
-     * <p>The guaranteed stream configurations listed in
-     * {@link #createCaptureSession createCaptureSession} are also guaranteed to work for
-     * {@link #createReprocessableCaptureSession createReprocessableCaptureSession}. In addition,
-     * the configurations in the tables below are also guaranteed for creating a reprocessable
+     * <p>The configurations in the tables below are guaranteed for creating a reprocessable
      * capture session if the camera device supports YUV reprocessing or PRIVATE reprocessing.
      * However, not all output targets used to create a reprocessable session may be used in a
      * {@link CaptureRequest} simultaneously. For devices that support only 1 output target in a
@@ -602,7 +785,7 @@
      * <p>LIMITED-level ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL}
      * {@code == }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED}) devices
      * support at least the following stream combinations for creating a reprocessable capture
-     * session in addition to those listed in {@link #createCaptureSession createCaptureSession} for
+     * session in addition to those listed earlier for regular captures for
      * {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED} devices:
      *
      * <table>
@@ -671,74 +854,30 @@
      * </table><br>
      * </p>
      *
-     * <p>Clients can access the above mandatory stream combination tables via
-     * {@link android.hardware.camera2.params.MandatoryStreamCombination}.</p>
+     * <h3>Constrained high-speed recording</h3>
      *
-     * @param inputConfig The configuration for the input {@link Surface}
-     * @param outputs The new set of Surfaces that should be made available as
-     *                targets for captured image data.
-     * @param callback The callback to notify about the status of the new capture session.
-     * @param handler The handler on which the callback should be invoked, or {@code null} to use
-     *                the current thread's {@link android.os.Looper looper}.
-     *
-     * @throws IllegalArgumentException if the input configuration is null or not supported, the set
-     *                                  of output Surfaces do not meet the requirements, the
-     *                                  callback is null, or the handler is null but the current
-     *                                  thread has no looper.
-     * @throws CameraAccessException if the camera device is no longer connected or has
-     *                               encountered a fatal error
-     * @throws IllegalStateException if the camera device has been closed
-     *
-     * @see #createCaptureSession
-     * @see CameraCaptureSession
-     * @see StreamConfigurationMap#getInputFormats
-     * @see StreamConfigurationMap#getInputSizes
-     * @see StreamConfigurationMap#getValidOutputFormatsForInput
-     * @see StreamConfigurationMap#getOutputSizes
-     * @see android.media.ImageWriter
-     * @see android.media.ImageReader
-     */
-    public abstract void createReprocessableCaptureSession(@NonNull InputConfiguration inputConfig,
-            @NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback,
-            @Nullable Handler handler)
-            throws CameraAccessException;
-
-    /**
-     * Create a new reprocessable camera capture session by providing the desired reprocessing
-     * input configuration and output {@link OutputConfiguration}
-     * to the camera device.
-     *
-     * @see #createReprocessableCaptureSession
-     * @see OutputConfiguration
-     *
-     */
-    public abstract void createReprocessableCaptureSessionByConfigurations(
-            @NonNull InputConfiguration inputConfig,
-            @NonNull List<OutputConfiguration> outputs,
-            @NonNull CameraCaptureSession.StateCallback callback,
-            @Nullable Handler handler)
-            throws CameraAccessException;
-
-    /**
-     * <p>Create a new constrained high speed capture session.</p>
-     *
-     * <p>The application can use normal capture session (created via {@link #createCaptureSession})
+     * <p>The application can use a
+     * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_REGULAR
+     * normal capture session}
      * for high speed capture if the desired high speed FPS ranges are advertised by
      * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES}, in which case all API
      * semantics associated with normal capture sessions applies.</p>
      *
-     * <p>The method creates a specialized capture session that is only targeted at high speed
-     * video recording (>=120fps) use case if the camera device supports high speed video
-     * capability (i.e., {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} contains
-     * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}).
-     * Therefore, it has special characteristics compared with a normal capture session:</p>
+     * <p>A
+     * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_HIGH_SPEED
+     * high-speed capture session}
+     * can be use for high speed video recording (>=120fps) when the camera device supports high
+     * speed video capability (i.e., {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES}
+     * contains {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}).
+     * A constrained high-speed capture session has special limitations compared with a normal
+     * capture session:</p>
      *
      * <ul>
      *
-     * <li>In addition to the output target Surface requirements specified by the
-     *   {@link #createCaptureSession} method, an active high speed capture session will support up
-     *   to 2 output Surfaces, though the application might choose to configure just one Surface
-     *   (e.g., preview only). All Surfaces must be either video encoder surfaces (acquired by
+     * <li>In addition to the output target Surface requirements specified above for regular
+     *   captures, a high speed capture session will only support up to 2 output Surfaces, though
+     *   the application might choose to configure just one Surface (e.g., preview only). All
+     *   Surfaces must be either video encoder surfaces (acquired by
      *   {@link android.media.MediaRecorder#getSurface} or
      *   {@link android.media.MediaCodec#createInputSurface}) or preview surfaces (obtained from
      *   {@link android.view.SurfaceView}, {@link android.graphics.SurfaceTexture} via
@@ -774,116 +913,6 @@
      *
      * </ul>
      *
-     * @param outputs The new set of Surfaces that should be made available as
-     *                targets for captured high speed image data.
-     * @param callback The callback to notify about the status of the new capture session.
-     * @param handler The handler on which the callback should be invoked, or {@code null} to use
-     *                the current thread's {@link android.os.Looper looper}.
-     *
-     * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
-     *                                  the callback is null, or the handler is null but the current
-     *                                  thread has no looper, or the camera device doesn't support
-     *                                  high speed video capability.
-     * @throws CameraAccessException if the camera device is no longer connected or has
-     *                               encountered a fatal error
-     * @throws IllegalStateException if the camera device has been closed
-     *
-     * @see #createCaptureSession
-     * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE
-     * @see StreamConfigurationMap#getHighSpeedVideoSizes
-     * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor
-     * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
-     * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
-     * @see CameraCaptureSession#captureBurst
-     * @see CameraCaptureSession#setRepeatingBurst
-     * @see CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList
-     */
-    public abstract void createConstrainedHighSpeedCaptureSession(@NonNull List<Surface> outputs,
-            @NonNull CameraCaptureSession.StateCallback callback,
-            @Nullable Handler handler)
-            throws CameraAccessException;
-
-    /**
-     * Standard camera operation mode.
-     *
-     * @see #createCustomCaptureSession
-     * @hide
-     */
-    @SystemApi
-    @TestApi
-    public static final int SESSION_OPERATION_MODE_NORMAL =
-            0; // ICameraDeviceUser.NORMAL_MODE;
-
-    /**
-     * Constrained high-speed operation mode.
-     *
-     * @see #createCustomCaptureSession
-     * @hide
-     */
-    @SystemApi
-    @TestApi
-    public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED =
-            1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;
-
-    /**
-     * First vendor-specific operating mode
-     *
-     * @see #createCustomCaptureSession
-     * @hide
-     */
-    @SystemApi
-    @TestApi
-    public static final int SESSION_OPERATION_MODE_VENDOR_START =
-            0x8000; // ICameraDeviceUser.VENDOR_MODE_START;
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = {"SESSION_OPERATION_MODE"}, value =
-            {SESSION_OPERATION_MODE_NORMAL,
-             SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED,
-             SESSION_OPERATION_MODE_VENDOR_START})
-    public @interface SessionOperatingMode {};
-
-    /**
-     * Create a new camera capture session with a custom operating mode.
-     *
-     * @param inputConfig The configuration for the input {@link Surface} if a reprocessing session
-     *                is desired, or {@code null} otherwise.
-     * @param outputs The new set of {@link OutputConfiguration OutputConfigurations} that should be
-     *                made available as targets for captured image data.
-     * @param operatingMode The custom operating mode to use; a nonnegative value, either a custom
-     *                vendor value or one of the SESSION_OPERATION_MODE_* values.
-     * @param callback The callback to notify about the status of the new capture session.
-     * @param handler The handler on which the callback should be invoked, or {@code null} to use
-     *                the current thread's {@link android.os.Looper looper}.
-     *
-     * @throws IllegalArgumentException if the input configuration is null or not supported, the set
-     *                                  of output Surfaces do not meet the requirements, the
-     *                                  callback is null, or the handler is null but the current
-     *                                  thread has no looper.
-     * @throws CameraAccessException if the camera device is no longer connected or has
-     *                               encountered a fatal error
-     * @throws IllegalStateException if the camera device has been closed
-     *
-     * @see #createCaptureSession
-     * @see #createReprocessableCaptureSession
-     * @see CameraCaptureSession
-     * @see OutputConfiguration
-     * @hide
-     */
-    @SystemApi
-    @TestApi
-    public abstract void createCustomCaptureSession(
-            InputConfiguration inputConfig,
-            @NonNull List<OutputConfiguration> outputs,
-            @SessionOperatingMode int operatingMode,
-            @NonNull CameraCaptureSession.StateCallback callback,
-            @Nullable Handler handler)
-            throws CameraAccessException;
-
-    /**
-     * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper
-     * object that aggregates all supported parameters.</p>
      *
      * @param config A session configuration (see {@link SessionConfiguration}).
      *
@@ -997,7 +1026,7 @@
      *
      * @see CaptureRequest.Builder
      * @see TotalCaptureResult
-     * @see CameraDevice#createReprocessableCaptureSession
+     * @see CameraDevice#createCaptureSession(android.hardware.camera2.params.SessionConfiguration)
      * @see android.media.ImageWriter
      */
     @NonNull
@@ -1028,7 +1057,8 @@
      * <p>This method performs a runtime check of a given {@link SessionConfiguration}. The result
      * confirms whether or not the passed session configuration can be successfully used to
      * create a camera capture session using
-     * {@link CameraDevice#createCaptureSession(SessionConfiguration)}.
+     * {@link CameraDevice#createCaptureSession(
+     * android.hardware.camera2.params.SessionConfiguration)}.
      * </p>
      *
      * <p>The method can be called at any point before, during and after active capture session.
diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java
index 555ff9a..47a897c 100644
--- a/core/java/android/hardware/camera2/params/SessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java
@@ -17,10 +17,11 @@
 
 package android.hardware.camera2.params;
 
+import static com.android.internal.util.Preconditions.*;
+
 import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
@@ -32,14 +33,12 @@
 import android.os.Parcelable;
 import android.util.Log;
 
-import java.util.Collections;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.concurrent.Executor;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-
-import static com.android.internal.util.Preconditions.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * A helper class that aggregates all supported arguments for capture session initialization.
@@ -61,6 +60,12 @@
      * A high speed session type that can only contain instances of {@link OutputConfiguration}.
      * The outputs can run using high speed FPS ranges. Calls to {@link #setInputConfiguration}
      * are not supported.
+     * <p>
+     * When using this type, the CameraCaptureSession returned by
+     * {@link android.hardware.camera2.CameraCaptureSession.StateCallback} can be cast to a
+     * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession} to access the extra
+     * methods for constrained high speed recording.
+     * </p>
      *
      * @see CameraDevice#createConstrainedHighSpeedCaptureSession
      */
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 43f3787..d43a619 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -20,6 +20,7 @@
 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;
@@ -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 d872009..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;
 
@@ -137,12 +164,19 @@
          * 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, @NonNull String supportedModelArch,
                 int maxSoundModels, int maxKeyphrases, int maxUsers, int recognitionModes,
                 boolean supportsCaptureTransition, int maxBufferMs,
                 boolean supportsConcurrentCapture, int powerConsumptionMw,
-                boolean returnsTriggerInEvent) {
+                boolean returnsTriggerInEvent, int audioCapabilities) {
             this.id = id;
             this.implementor = requireNonNull(implementor);
             this.description = requireNonNull(description);
@@ -158,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
@@ -187,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,
                     supportedModelArch, maxSoundModels, maxKeyphrases, maxUsers, recognitionModes,
                     supportsCaptureTransition, maxBufferMs, supportsConcurrentCapture,
-                    powerConsumptionMw, returnsTriggerInEvent);
+                    powerConsumptionMw, returnsTriggerInEvent, audioCapabilities);
         }
 
         @Override
@@ -210,6 +246,7 @@
             dest.writeByte((byte) (supportsConcurrentCapture ? 1 : 0));
             dest.writeInt(powerConsumptionMw);
             dest.writeByte((byte) (returnsTriggerInEvent ? 1 : 0));
+            dest.writeInt(audioCapabilities);
         }
 
         @Override
@@ -227,7 +264,8 @@
                     + ", supportsCaptureTransition=" + supportsCaptureTransition + ", maxBufferMs="
                     + maxBufferMs + ", supportsConcurrentCapture=" + supportsConcurrentCapture
                     + ", powerConsumptionMw=" + powerConsumptionMw
-                    + ", returnsTriggerInEvent=" + returnsTriggerInEvent + "]";
+                    + ", returnsTriggerInEvent=" + returnsTriggerInEvent
+                    + ", audioCapabilities=" + audioCapabilities + "]";
         }
     }
 
@@ -1049,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
@@ -1075,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
@@ -1084,6 +1138,7 @@
             dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0));
             dest.writeTypedArray(keyphrases, flags);
             dest.writeBlob(data);
+            dest.writeInt(audioCapabilities);
         }
 
         @Override
@@ -1095,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/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 03d4200..4bf3e90 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -3121,8 +3121,6 @@
     @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
     public int registerNetworkProvider(@NonNull NetworkProvider provider) {
         if (provider.getProviderId() != NetworkProvider.ID_NONE) {
-            // TODO: Provide a better method for checking this by moving NetworkFactory.SerialNumber
-            // out of NetworkFactory.
             throw new IllegalStateException("NetworkProviders can only be registered once");
         }
 
@@ -3175,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);
     }
 
     /**
@@ -3187,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/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index e6a0379..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;
@@ -153,7 +153,8 @@
     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/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/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/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 2e7ac3f5..f369064 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4142,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
@@ -4205,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";
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/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index d7c6d0f..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,30 @@
     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 = {
@@ -448,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
@@ -711,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);
         }
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index fa994ba..0892c94 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -16,6 +16,8 @@
 
 package android.util;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -25,172 +27,270 @@
 import android.net.NetworkInfo;
 import android.net.SntpClient;
 import android.os.SystemClock;
-import android.os.TimestampedValue;
 import android.provider.Settings;
 import android.text.TextUtils;
 
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+
 /**
- * {@link TrustedTime} that connects with a remote NTP server as its trusted
- * time source.
+ * A singleton that connects with a remote NTP server as its trusted time source. This class
+ * is thread-safe. The {@link #forceRefresh()} method is synchronous, i.e. it may occupy the
+ * current thread while performing an NTP request. All other threads calling {@link #forceRefresh()}
+ * will block during that request.
  *
  * @hide
  */
 public class NtpTrustedTime implements TrustedTime {
+
+    /**
+     * The result of a successful NTP query.
+     *
+     * @hide
+     */
+    public static class TimeResult {
+        private final long mTimeMillis;
+        private final long mElapsedRealtimeMillis;
+        private final long mCertaintyMillis;
+
+        public TimeResult(long timeMillis, long elapsedRealtimeMillis, long certaintyMillis) {
+            mTimeMillis = timeMillis;
+            mElapsedRealtimeMillis = elapsedRealtimeMillis;
+            mCertaintyMillis = certaintyMillis;
+        }
+
+        public long getTimeMillis() {
+            return mTimeMillis;
+        }
+
+        public long getElapsedRealtimeMillis() {
+            return mElapsedRealtimeMillis;
+        }
+
+        public long getCertaintyMillis() {
+            return mCertaintyMillis;
+        }
+
+        /** Calculates and returns the current time accounting for the age of this result. */
+        public long currentTimeMillis() {
+            return mTimeMillis + getAgeMillis();
+        }
+
+        /** Calculates and returns the age of this result. */
+        public long getAgeMillis() {
+            return SystemClock.elapsedRealtime() - mElapsedRealtimeMillis;
+        }
+
+        @Override
+        public String toString() {
+            return "TimeResult{"
+                    + "mTimeMillis=" + mTimeMillis
+                    + ", mElapsedRealtimeMillis=" + mElapsedRealtimeMillis
+                    + ", mCertaintyMillis=" + mCertaintyMillis
+                    + '}';
+        }
+    }
+
     private static final String TAG = "NtpTrustedTime";
     private static final boolean LOGD = false;
 
     private static NtpTrustedTime sSingleton;
-    private static Context sContext;
 
-    private final String mServer;
-    private final long mTimeout;
+    @NonNull
+    private final Context mContext;
 
-    private ConnectivityManager mCM;
+    /**
+     * A supplier that returns the ConnectivityManager. The Supplier can return null if
+     * ConnectivityService isn't running yet.
+     */
+    private final Supplier<ConnectivityManager> mConnectivityManagerSupplier =
+            new Supplier<ConnectivityManager>() {
+        private ConnectivityManager mConnectivityManager;
 
-    private boolean mHasCache;
-    private long mCachedNtpTime;
-    private long mCachedNtpElapsedRealtime;
-    private long mCachedNtpCertainty;
+        @Nullable
+        @Override
+        public synchronized ConnectivityManager get() {
+            // We can't do this at initialization time: ConnectivityService might not be running
+            // yet.
+            if (mConnectivityManager == null) {
+                mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+            }
+            return mConnectivityManager;
+        }
+    };
 
-    private NtpTrustedTime(String server, long timeout) {
-        if (LOGD) Log.d(TAG, "creating NtpTrustedTime using " + server);
-        mServer = server;
-        mTimeout = timeout;
+    // Declared volatile and accessed outside of synchronized blocks to avoid blocking reads during
+    // forceRefresh().
+    private volatile TimeResult mTimeResult;
+
+    private NtpTrustedTime(Context context) {
+        mContext = Objects.requireNonNull(context);
     }
 
     @UnsupportedAppUsage
     public static synchronized NtpTrustedTime getInstance(Context context) {
         if (sSingleton == null) {
-            final Resources res = context.getResources();
-            final ContentResolver resolver = context.getContentResolver();
-
-            final String defaultServer = res.getString(
-                    com.android.internal.R.string.config_ntpServer);
-            final long defaultTimeout = res.getInteger(
-                    com.android.internal.R.integer.config_ntpTimeout);
-
-            final String secureServer = Settings.Global.getString(
-                    resolver, Settings.Global.NTP_SERVER);
-            final long timeout = Settings.Global.getLong(
-                    resolver, Settings.Global.NTP_TIMEOUT, defaultTimeout);
-
-            final String server = secureServer != null ? secureServer : defaultServer;
-            sSingleton = new NtpTrustedTime(server, timeout);
-            sContext = context;
+            Context appContext = context.getApplicationContext();
+            sSingleton = new NtpTrustedTime(appContext);
         }
-
         return sSingleton;
     }
 
-    @Override
     @UnsupportedAppUsage
     public boolean forceRefresh() {
-        // We can't do this at initialization time: ConnectivityService might not be running yet.
         synchronized (this) {
-            if (mCM == null) {
-                mCM = sContext.getSystemService(ConnectivityManager.class);
+            NtpConnectionInfo connectionInfo = getNtpConnectionInfo();
+            if (connectionInfo == null) {
+                // missing server config, so no trusted time available
+                if (LOGD) Log.d(TAG, "forceRefresh: invalid server config");
+                return false;
             }
-        }
 
-        final Network network = mCM == null ? null : mCM.getActiveNetwork();
-        return forceRefresh(network);
-    }
-
-    public boolean forceRefresh(Network network) {
-        if (TextUtils.isEmpty(mServer)) {
-            // missing server, so no trusted time available
-            return false;
-        }
-
-        // We can't do this at initialization time: ConnectivityService might not be running yet.
-        synchronized (this) {
-            if (mCM == null) {
-                mCM = sContext.getSystemService(ConnectivityManager.class);
+            ConnectivityManager connectivityManager = mConnectivityManagerSupplier.get();
+            if (connectivityManager == null) {
+                if (LOGD) Log.d(TAG, "forceRefresh: no ConnectivityManager");
+                return false;
             }
-        }
+            final Network network = connectivityManager.getActiveNetwork();
+            final NetworkInfo ni = connectivityManager.getNetworkInfo(network);
+            if (ni == null || !ni.isConnected()) {
+                if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
+                return false;
+            }
 
-        final NetworkInfo ni = mCM == null ? null : mCM.getNetworkInfo(network);
-        if (ni == null || !ni.isConnected()) {
-            if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
-            return false;
-        }
-
-
-        if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
-        final SntpClient client = new SntpClient();
-        if (client.requestTime(mServer, (int) mTimeout, network)) {
-            mHasCache = true;
-            mCachedNtpTime = client.getNtpTime();
-            mCachedNtpElapsedRealtime = client.getNtpTimeReference();
-            mCachedNtpCertainty = client.getRoundTripTime() / 2;
-            return true;
-        } else {
-            return false;
+            if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
+            final SntpClient client = new SntpClient();
+            final String serverName = connectionInfo.getServer();
+            final int timeoutMillis = connectionInfo.getTimeoutMillis();
+            if (client.requestTime(serverName, timeoutMillis, network)) {
+                long ntpCertainty = client.getRoundTripTime() / 2;
+                mTimeResult = new TimeResult(
+                        client.getNtpTime(), client.getNtpTimeReference(), ntpCertainty);
+                return true;
+            } else {
+                return false;
+            }
         }
     }
 
-    @Override
+    /**
+     * Only kept for UnsupportedAppUsage.
+     *
+     * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+     */
+    @Deprecated
     @UnsupportedAppUsage
     public boolean hasCache() {
-        return mHasCache;
+        return mTimeResult != null;
     }
 
+    /**
+     * Only kept for UnsupportedAppUsage.
+     *
+     * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+     */
+    @Deprecated
     @Override
     public long getCacheAge() {
-        if (mHasCache) {
-            return SystemClock.elapsedRealtime() - mCachedNtpElapsedRealtime;
+        TimeResult timeResult = mTimeResult;
+        if (timeResult != null) {
+            return SystemClock.elapsedRealtime() - timeResult.getElapsedRealtimeMillis();
         } else {
             return Long.MAX_VALUE;
         }
     }
 
-    @Override
-    public long getCacheCertainty() {
-        if (mHasCache) {
-            return mCachedNtpCertainty;
-        } else {
-            return Long.MAX_VALUE;
-        }
-    }
-
-    @Override
+    /**
+     * Only kept for UnsupportedAppUsage.
+     *
+     * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+     */
+    @Deprecated
     @UnsupportedAppUsage
     public long currentTimeMillis() {
-        if (!mHasCache) {
+        TimeResult timeResult = mTimeResult;
+        if (timeResult == null) {
             throw new IllegalStateException("Missing authoritative time source");
         }
         if (LOGD) Log.d(TAG, "currentTimeMillis() cache hit");
 
         // current time is age after the last ntp cache; callers who
-        // want fresh values will hit makeAuthoritative() first.
-        return mCachedNtpTime + getCacheAge();
-    }
-
-    @UnsupportedAppUsage
-    public long getCachedNtpTime() {
-        if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit");
-        return mCachedNtpTime;
-    }
-
-    @UnsupportedAppUsage
-    public long getCachedNtpTimeReference() {
-        return mCachedNtpElapsedRealtime;
+        // want fresh values will hit forceRefresh() first.
+        return timeResult.currentTimeMillis();
     }
 
     /**
-     * Returns the combination of {@link #getCachedNtpTime()} and {@link
-     * #getCachedNtpTimeReference()} as a {@link TimestampedValue}. This method is useful when
-     * passing the time to another component that will adjust for elapsed time.
+     * Only kept for UnsupportedAppUsage.
      *
-     * @throws IllegalStateException if there is no cached value
+     * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
      */
-    public TimestampedValue<Long> getCachedNtpTimeSignal() {
-        if (!mHasCache) {
-            throw new IllegalStateException("Missing authoritative time source");
-        }
-        if (LOGD) Log.d(TAG, "getCachedNtpTimeSignal() cache hit");
-
-        return new TimestampedValue<>(mCachedNtpElapsedRealtime, mCachedNtpTime);
+    @Deprecated
+    @UnsupportedAppUsage
+    public long getCachedNtpTime() {
+        if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit");
+        TimeResult timeResult = mTimeResult;
+        return timeResult == null ? 0 : timeResult.getTimeMillis();
     }
 
+    /**
+     * Only kept for UnsupportedAppUsage.
+     *
+     * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public long getCachedNtpTimeReference() {
+        TimeResult timeResult = mTimeResult;
+        return timeResult == null ? 0 : timeResult.getElapsedRealtimeMillis();
+    }
+
+    /**
+     * Returns an object containing the latest NTP information available. Can return {@code null} if
+     * no information is available.
+     */
+    @Nullable
+    public TimeResult getCachedTimeResult() {
+        return mTimeResult;
+    }
+
+    private static class NtpConnectionInfo {
+
+        @NonNull private final String mServer;
+        private final int mTimeoutMillis;
+
+        NtpConnectionInfo(@NonNull String server, int timeoutMillis) {
+            mServer = Objects.requireNonNull(server);
+            mTimeoutMillis = timeoutMillis;
+        }
+
+        @NonNull
+        public String getServer() {
+            return mServer;
+        }
+
+        int getTimeoutMillis() {
+            return mTimeoutMillis;
+        }
+    }
+
+    @GuardedBy("this")
+    private NtpConnectionInfo getNtpConnectionInfo() {
+        final ContentResolver resolver = mContext.getContentResolver();
+
+        final Resources res = mContext.getResources();
+        final String defaultServer = res.getString(
+                com.android.internal.R.string.config_ntpServer);
+        final int defaultTimeoutMillis = res.getInteger(
+                com.android.internal.R.integer.config_ntpTimeout);
+
+        final String secureServer = Settings.Global.getString(
+                resolver, Settings.Global.NTP_SERVER);
+        final int timeoutMillis = Settings.Global.getInt(
+                resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis);
+
+        final String server = secureServer != null ? secureServer : defaultServer;
+        return TextUtils.isEmpty(server) ? null : new NtpConnectionInfo(server, timeoutMillis);
+    }
 }
diff --git a/core/java/android/util/TrustedTime.java b/core/java/android/util/TrustedTime.java
index 1360f87..f41fe85 100644
--- a/core/java/android/util/TrustedTime.java
+++ b/core/java/android/util/TrustedTime.java
@@ -20,42 +20,48 @@
 
 /**
  * Interface that provides trusted time information, possibly coming from an NTP
- * server. Implementations may cache answers until {@link #forceRefresh()}.
+ * server.
  *
  * @hide
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
  */
 public interface TrustedTime {
     /**
      * Force update with an external trusted time source, returning {@code true}
      * when successful.
+     *
+     * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
      */
+    @Deprecated
     @UnsupportedAppUsage
     public boolean forceRefresh();
 
     /**
      * Check if this instance has cached a response from a trusted time source.
+     *
+     * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
      */
+    @Deprecated
     @UnsupportedAppUsage
-    public boolean hasCache();
+    boolean hasCache();
 
     /**
      * Return time since last trusted time source contact, or
      * {@link Long#MAX_VALUE} if never contacted.
+     *
+     * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
      */
+    @Deprecated
     @UnsupportedAppUsage
     public long getCacheAge();
 
     /**
-     * Return certainty of cached trusted time in milliseconds, or
-     * {@link Long#MAX_VALUE} if never contacted. Smaller values are more
-     * precise.
-     */
-    public long getCacheCertainty();
-
-    /**
      * Return current time similar to {@link System#currentTimeMillis()},
      * possibly using a cached authoritative time source.
+     *
+     * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
      */
+    @Deprecated
     @UnsupportedAppUsage
-    public long currentTimeMillis();
+    long currentTimeMillis();
 }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 9e23c28..9fbc1b7 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -350,7 +350,12 @@
      * (boolean) Whether screenshot flow going to the corner (instead of shown in a notification)
      * is enabled.
      */
-    public static final String SCREENSHOT_CORNER_FLOW = "screenshot_corner_flow";
+    public static final String SCREENSHOT_CORNER_FLOW = "enable_screenshot_corner_flow";
+
+    /**
+     * (boolean) Whether scrolling screenshots are enabled.
+     */
+    public static final String SCREENSHOT_SCROLLING_ENABLED = "enable_screenshot_scrolling";
 
     private SystemUiDeviceConfigFlags() {
     }
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/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_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 5039213..466544c 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -746,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)
@@ -757,9 +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) {
+      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/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index b83b31c..cd3887e 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2553,4 +2553,8 @@
     // OS: R
     MANAGE_EXTERNAL_STORAGE = 1822;
 
+    // Open: Settings > DND > People
+    // OS: R
+    DND_PEOPLE = 1823;
+
 }
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 9054d54..0fca1d1 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -154,4 +154,5 @@
   SET_AUTO_TIME = 127;
   SET_AUTO_TIME_ZONE = 128;
   SET_PACKAGES_PROTECTED = 129;
+  SET_FACTORY_RESET_PROTECTION = 130;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c050146..d4768c0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -445,6 +445,7 @@
 
     <protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" />
     <protected-broadcast android:name="android.app.action.SYSTEM_UPDATE_POLICY_CHANGED" />
+    <protected-broadcast android:name="android.app.action.RESET_PROTECTION_POLICY_CHANGED" />
     <protected-broadcast android:name="android.app.action.DEVICE_OWNER_CHANGED" />
     <protected-broadcast android:name="android.app.action.MANAGED_USER_CREATED" />
 
@@ -640,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" />
@@ -1791,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
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 37c9710..6cf6a68 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3030,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/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/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/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/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/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl
index 28bf84d..5dd0b1c 100644
--- a/media/java/android/media/IMediaRoute2Provider.aidl
+++ b/media/java/android/media/IMediaRoute2Provider.aidl
@@ -24,7 +24,8 @@
  */
 oneway interface IMediaRoute2Provider {
     void setClient(IMediaRoute2ProviderClient client);
-    void requestCreateSession(String packageName, String routeId, String routeType, long requestId);
+    void requestCreateSession(String packageName, String routeId,
+            String routeFeature, long requestId);
     void releaseSession(String sessionId);
 
     void selectRoute(String sessionId, String routeId);
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 e8af21e..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 notifyRouteTypesChanged(String packageName, in List<String> routeTypes);
+    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 b573f64..3cdaa07 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -22,8 +22,8 @@
 import android.media.IMediaRouterClient;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouterClientState;
-import android.media.RouteDiscoveryRequest;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
 
 /**
  * {@hide}
@@ -52,8 +52,8 @@
     void requestUpdateVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int direction);
 
     void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route,
-            String routeType, int requestId);
-    void setDiscoveryRequest2(IMediaRouter2Client client, in RouteDiscoveryRequest request);
+            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);
@@ -70,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/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 1ed53d9..021e23e 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -36,7 +36,6 @@
 
 /**
  * Describes the properties of a route.
- * @hide
  */
 public final class MediaRoute2Info implements Parcelable {
     @NonNull
@@ -56,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.
@@ -99,16 +98,15 @@
 
     /** @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;
 
@@ -118,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
@@ -126,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;
 
@@ -152,7 +149,7 @@
     @Nullable
     final String mClientPackageName;
     @NonNull
-    final List<String> mRouteTypes;
+    final List<String> mFeatures;
     final int mVolume;
     final int mVolumeMax;
     final int mVolumeHandling;
@@ -168,7 +165,7 @@
         mConnectionState = builder.mConnectionState;
         mIconUri = builder.mIconUri;
         mClientPackageName = builder.mClientPackageName;
-        mRouteTypes = builder.mRouteTypes;
+        mFeatures = builder.mFeatures;
         mVolume = builder.mVolume;
         mVolumeMax = builder.mVolumeMax;
         mVolumeHandling = builder.mVolumeHandling;
@@ -184,7 +181,7 @@
         mConnectionState = in.readInt();
         mIconUri = in.readParcelable(null);
         mClientPackageName = in.readString();
-        mRouteTypes = in.createStringArrayList();
+        mFeatures = in.createStringArrayList();
         mVolume = in.readInt();
         mVolumeMax = in.readInt();
         mVolumeHandling = in.readInt();
@@ -223,7 +220,7 @@
                 && (mConnectionState == other.mConnectionState)
                 && Objects.equals(mIconUri, other.mIconUri)
                 && Objects.equals(mClientPackageName, other.mClientPackageName)
-                && Objects.equals(mRouteTypes, other.mRouteTypes)
+                && Objects.equals(mFeatures, other.mFeatures)
                 && (mVolume == other.mVolume)
                 && (mVolumeMax == other.mVolumeMax)
                 && (mVolumeHandling == other.mVolumeHandling)
@@ -235,7 +232,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mId, mName, mDescription, mConnectionState, mIconUri,
-                mRouteTypes, mVolume, mVolumeMax, mVolumeHandling, mDeviceType);
+                mFeatures, mVolume, mVolumeMax, mVolumeHandling, mDeviceType);
     }
 
     /**
@@ -245,7 +242,7 @@
      * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method
      * can be different from what was set in {@link MediaRoute2ProviderService}.
      *
-     * @see Builder#setId(String)
+     * @see Builder#Builder(String, CharSequence)
      */
     @NonNull
     public String getId() {
@@ -257,7 +254,7 @@
     }
 
     /**
-     * Gets the original id set by {@link Builder#setId(String)}.
+     * Gets the original id set by {@link Builder#Builder(String, CharSequence)}.
      * @hide
      */
     @NonNull
@@ -324,16 +321,16 @@
      * Gets the supported categories of the route.
      */
     @NonNull
-    public List<String> getRouteTypes() {
-        return mRouteTypes;
+    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() {
@@ -369,15 +366,15 @@
     }
 
     /**
-     * Returns if the route contains at least one of the specified route types.
+     * Returns if the route has at least one of the specified route features.
      *
-     * @param routeTypes the list of route types to consider
-     * @return true if the route contains at least one type in the list
+     * @param features the list of route features to consider
+     * @return true if the route has at least one feature in the list
      */
-    public boolean containsRouteTypes(@NonNull Collection<String> routeTypes) {
-        Objects.requireNonNull(routeTypes, "routeTypes must not be null");
-        for (String routeType : routeTypes) {
-            if (getRouteTypes().contains(routeType)) {
+    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;
             }
         }
@@ -398,7 +395,7 @@
         dest.writeInt(mConnectionState);
         dest.writeParcelable(mIconUri, flags);
         dest.writeString(mClientPackageName);
-        dest.writeStringList(mRouteTypes);
+        dest.writeStringList(mFeatures);
         dest.writeInt(mVolume);
         dest.writeInt(mVolumeMax);
         dest.writeInt(mVolumeHandling);
@@ -428,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> mRouteTypes;
+        List<String> mFeatures;
         int mVolume;
         int mVolumeMax;
         int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
@@ -444,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);
-            mRouteTypes = 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);
-            setRouteTypes(routeInfo.mRouteTypes);
+            mFeatures = new ArrayList<>(routeInfo.mFeatures);
             setVolume(routeInfo.mVolume);
             setVolumeMax(routeInfo.mVolumeMax);
             setVolumeHandling(routeInfo.mVolumeHandling);
@@ -475,26 +488,6 @@
         }
 
         /**
-         * Sets the unique id of the route. The value given here must be unique for each of your
-         * route.
-         * <p>
-         * In order to ensure uniqueness in {@link MediaRouter2} side, the value of
-         * {@link MediaRoute2Info#getId()} can be different from what was set in
-         * {@link MediaRoute2ProviderService}.
-         * </p>
-         *
-         * @see MediaRoute2Info#getId()
-         */
-        @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
          */
@@ -508,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;
         }
@@ -569,35 +552,35 @@
         }
 
         /**
-         * Sets the types of the route.
+         * Clears the features of the route.
          */
         @NonNull
-        public Builder setRouteTypes(@NonNull Collection<String> routeTypes) {
-            mRouteTypes = new ArrayList<>();
-            return addRouteTypes(routeTypes);
+        public Builder clearFeatures() {
+            mFeatures = new ArrayList<>();
+            return this;
         }
 
         /**
-         * Adds types for the route.
+         * Adds features for the route.
          */
         @NonNull
-        public Builder addRouteTypes(@NonNull Collection<String> routeTypes) {
-            Objects.requireNonNull(routeTypes, "routeTypes must not be null");
-            for (String routeType: routeTypes) {
-                addRouteType(routeType);
+        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 type for the route.
+         * Adds a feature for the route.
          */
         @NonNull
-        public Builder addRouteType(@NonNull String routeType) {
-            if (TextUtils.isEmpty(routeType)) {
-                throw new IllegalArgumentException("routeType 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");
             }
-            mRouteTypes.add(routeType);
+            mFeatures.add(feature);
             return this;
         }
 
@@ -642,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 e2f246c..c9a2ec7 100644
--- a/media/java/android/media/MediaRoute2ProviderInfo.java
+++ b/media/java/android/media/MediaRoute2ProviderInfo.java
@@ -93,6 +93,9 @@
 
     /**
      * Gets the route for the given route id or null if no matching route exists.
+     * Please note that id should be original id.
+     *
+     * @see MediaRoute2Info#getOriginalId()
      */
     @Nullable
     public MediaRoute2Info getRoute(@NonNull String routeId) {
@@ -168,7 +171,7 @@
                 MediaRoute2Info routeWithProviderId = new MediaRoute2Info.Builder(entry.getValue())
                         .setProviderId(mUniqueId)
                         .build();
-                newRoutes.put(routeWithProviderId.getId(), routeWithProviderId);
+                newRoutes.put(routeWithProviderId.getOriginalId(), routeWithProviderId);
             }
 
             mRoutes.clear();
@@ -183,14 +186,14 @@
         public Builder addRoute(@NonNull MediaRoute2Info route) {
             Objects.requireNonNull(route, "route must not be null");
 
-            if (mRoutes.containsValue(route)) {
-                throw new IllegalArgumentException("route descriptor already added");
+            if (mRoutes.containsKey(route.getOriginalId())) {
+                throw new IllegalArgumentException("A route with the same id is already added");
             }
             if (mUniqueId != null) {
-                mRoutes.put(route.getId(),
+                mRoutes.put(route.getOriginalId(),
                         new MediaRoute2Info.Builder(route).setProviderId(mUniqueId).build());
             } else {
-                mRoutes.put(route.getId(), route);
+                mRoutes.put(route.getOriginalId(), route);
             }
             return this;
         }
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 24b65ba..1cd5dfa 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -36,12 +36,22 @@
 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";
@@ -56,13 +66,14 @@
     private MediaRoute2ProviderInfo mProviderInfo;
 
     @GuardedBy("mSessionLock")
-    private ArrayMap<String, 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())) {
@@ -79,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);
@@ -88,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);
 
@@ -96,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);
 
@@ -105,9 +119,10 @@
      * @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(@NonNull String sessionId) {
+    public final RoutingSessionInfo getSessionInfo(@NonNull String sessionId) {
         if (TextUtils.isEmpty(sessionId)) {
             throw new IllegalArgumentException("sessionId must not be empty");
         }
@@ -117,10 +132,11 @@
     }
 
     /**
-     * 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());
         }
@@ -129,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");
         String sessionId = sessionInfo.getId();
-        if (sessionInfo.getSelectedRoutes().isEmpty()) {
-            releaseSession(sessionId);
-            return;
-        }
 
         synchronized (mSessionLock) {
             if (mSessionInfo.containsKey(sessionId)) {
@@ -161,7 +173,7 @@
      * 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");
 
         String sessionId = sessionInfo.getId();
@@ -189,14 +201,16 @@
      * controlled, pass a {@link Bundle} that contains how to control it.
      *
      * @param sessionInfo information of the new session.
-     *                    The {@link RouteSessionInfo#getId() 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) {
             String sessionId = sessionInfo.getId();
             synchronized (mSessionLock) {
@@ -224,14 +238,15 @@
      * {@link #onDestroySession} is called if the session is released.
      *
      * @param sessionId id of the session to be released
-     * @see #onDestroySession(String, RouteSessionInfo)
+     * @see #onDestroySession(String, RoutingSessionInfo)
+     * @hide
      */
     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);
         }
@@ -245,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 routeType the route type 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 routeType, long requestId);
+            @NonNull String routeFeature, long requestId);
 
     /**
      * Called when a session is about to be destroyed.
@@ -267,71 +283,78 @@
      * @param sessionId id of the session being destroyed.
      * @param lastSessionInfo information of the session being destroyed.
      * @see #releaseSession(String)
+     * @hide
      */
     public abstract void onDestroySession(@NonNull String sessionId,
-            @NonNull RouteSessionInfo lastSessionInfo);
+            @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(@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(@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(@NonNull String sessionId, @NonNull String routeId);
 
     /**
-     * Called when the {@link RouteDiscoveryRequest discovery request} has changed.
+     * Called when the {@link RouteDiscoveryPreference discovery preference} has changed.
      * <p>
      * Whenever an application registers a {@link MediaRouter2.RouteCallback callback},
-     * it also provides a discovery request to specify types of routes that it is interested in.
-     * The media router combines all of these discovery request into a single discovery request
-     * and notifies each provider.
+     * 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 RouteDiscoveryRequest#getRouteTypes() route types}
-     * in the discovery request 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 types.
+     * 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 request the new discovery request
+     * @param preference the new discovery preference
      */
-    public void onDiscoveryRequestChanged(@NonNull RouteDiscoveryRequest request) {}
+    public void onDiscoveryPreferenceChanged(@NonNull RouteDiscoveryPreference preference) {}
 
     /**
-     * Updates provider info and publishes routes and session info.
+     * Updates routes of the provider and notifies the system media router service.
      */
-    public final void updateProviderInfo(@NonNull MediaRoute2ProviderInfo providerInfo) {
-        mProviderInfo = Objects.requireNonNull(providerInfo, "providerInfo must not be null");
+    public final void notifyRoutes(@NonNull Collection<MediaRoute2Info> routes) {
+        Objects.requireNonNull(routes, "routes must not be null");
+        mProviderInfo = new MediaRoute2ProviderInfo.Builder()
+                .addRoutes(routes)
+                .build();
         schedulePublishState();
     }
 
@@ -355,7 +378,7 @@
             return;
         }
 
-        List<RouteSessionInfo> sessionInfos;
+        List<RoutingSessionInfo> sessionInfos;
         synchronized (mSessionLock) {
             sessionInfos = new ArrayList<>(mSessionInfo.values());
         }
@@ -384,12 +407,12 @@
 
         @Override
         public void requestCreateSession(String packageName, String routeId,
-                String routeType, long requestId) {
+                String routeFeature, long requestId) {
             if (!checkCallerisSystem()) {
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession,
-                    MediaRoute2ProviderService.this, packageName, routeId, routeType,
+                    MediaRoute2ProviderService.this, packageName, routeId, routeFeature,
                     requestId));
         }
         @Override
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 8ebf617..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,7 +34,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 
-import java.lang.annotation.Retention;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -50,50 +46,13 @@
 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 RouteDiscoveryRequest mDiscoveryRequest = RouteDiscoveryRequest.EMPTY;
+    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) {
@@ -193,12 +153,12 @@
      */
     public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull RouteCallback routeCallback,
-            @NonNull RouteDiscoveryRequest request) {
+            @NonNull RouteDiscoveryPreference preference) {
         Objects.requireNonNull(executor, "executor must not be null");
         Objects.requireNonNull(routeCallback, "callback must not be null");
-        Objects.requireNonNull(request, "request must not be null");
+        Objects.requireNonNull(preference, "preference must not be null");
 
-        RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, request);
+        RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, preference);
         if (!mRouteCallbackRecords.addIfAbsent(record)) {
             Log.w(TAG, "Ignoring the same callback");
             return;
@@ -210,15 +170,13 @@
                 try {
                     mMediaRouterService.registerClient2(client, mPackageName);
                     updateDiscoveryRequestLocked();
-                    mMediaRouterService.setDiscoveryRequest2(client, mDiscoveryRequest);
+                    mMediaRouterService.setDiscoveryRequest2(client, mDiscoveryPreference);
                     mClient = client;
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to register media router.", ex);
                 }
             }
         }
-
-        //TODO: Update discovery request here.
     }
 
     /**
@@ -251,8 +209,8 @@
     }
 
     private void updateDiscoveryRequestLocked() {
-        mDiscoveryRequest = new RouteDiscoveryRequest.Builder(
-                mRouteCallbackRecords.stream().map(record -> record.mRequest).collect(
+        mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
+                mRouteCallbackRecords.stream().map(record -> record.mPreference).collect(
                         Collectors.toList())).build();
     }
 
@@ -261,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 contains at least one of the route types in discovery
-     * requests registered 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() {
@@ -272,7 +230,7 @@
 
                 List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
                 for (MediaRoute2Info route : mRoutes.values()) {
-                    if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) {
+                    if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
                         filteredRoutes.add(route);
                     }
                 }
@@ -283,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,
@@ -309,6 +268,7 @@
      *
      * @param callback the callback to unregister
      * @see #registerSessionCallback
+     * @hide
      */
     @NonNull
     public void unregisterSessionCallback(@NonNull SessionCallback callback) {
@@ -324,25 +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 routeType the route type 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 routeType) {
+            @NonNull String routeFeature) {
         Objects.requireNonNull(route, "route must not be null");
-        if (TextUtils.isEmpty(routeType)) {
-            throw new IllegalArgumentException("routeType 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 routeType
+        // TODO: Check the route supports the given routeFeature
 
         final int requestId;
         requestId = mSessionCreationRequestCnt.getAndIncrement();
 
-        SessionCreationRequest request = new SessionCreationRequest(requestId, route, routeType);
+        SessionCreationRequest request = new SessionCreationRequest(requestId, route, routeFeature);
         mSessionCreationRequests.add(request);
 
         Client2 client;
@@ -351,7 +312,7 @@
         }
         if (client != null) {
             try {
-                mMediaRouterService.requestCreateSession(client, route, routeType, 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,
@@ -365,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
@@ -392,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");
@@ -416,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");
@@ -442,7 +406,7 @@
         synchronized (sRouterLock) {
             for (MediaRoute2Info route : routes) {
                 mRoutes.put(route.getId(), route);
-                if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) {
+                if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
                     addedRoutes.add(route);
                 }
             }
@@ -458,7 +422,7 @@
         synchronized (sRouterLock) {
             for (MediaRoute2Info route : routes) {
                 mRoutes.remove(route.getId());
-                if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) {
+                if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
                     removedRoutes.add(route);
                 }
             }
@@ -474,7 +438,7 @@
         synchronized (sRouterLock) {
             for (MediaRoute2Info route : routes) {
                 mRoutes.put(route.getId(), route);
-                if (route.containsRouteTypes(mDiscoveryRequest.getRouteTypes())) {
+                if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
                     changedRoutes.add(route);
                 }
             }
@@ -491,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) {
@@ -504,27 +468,27 @@
             mSessionCreationRequests.remove(matchingRequest);
 
             MediaRoute2Info requestedRoute = matchingRequest.mRoute;
-            String requestedRouteType = matchingRequest.mRouteType;
+            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, requestedRouteType);
+                notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
                 return;
-            } else if (!TextUtils.equals(requestedRouteType,
-                    sessionInfo.getRouteType())) {
-                Log.w(TAG, "The session has different route type from what we requested. "
-                        + "(requested=" + requestedRouteType
-                        + ", actual=" + sessionInfo.getRouteType()
+            } 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, requestedRouteType);
+                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, requestedRouteType);
+                notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
                 return;
             } else if (!TextUtils.equals(requestedRoute.getProviderId(),
                     sessionInfo.getProviderId())) {
@@ -532,29 +496,29 @@
                         + "(requested route's providerId=" + requestedRoute.getProviderId()
                         + ", actual providerId=" + sessionInfo.getProviderId()
                         + ")");
-                notifySessionCreationFailed(requestedRoute, requestedRouteType);
+                notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
                 return;
             }
         }
 
         if (sessionInfo != null) {
-            RouteSessionController controller = new RouteSessionController(sessionInfo);
+            RoutingController controller = new RoutingController(sessionInfo);
             synchronized (sRouterLock) {
-                mSessionControllers.put(controller.getSessionId(), 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.getId());
+            matchingController = mRoutingControllers.get(sessionInfo.getId());
         }
 
         if (matchingController == null) {
@@ -563,27 +527,27 @@
             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.getId();
-        RouteSessionController matchingController;
+        RoutingController matchingController;
         synchronized (sRouterLock) {
-            matchingController = mSessionControllers.get(uniqueSessionId);
+            matchingController = mRoutingControllers.get(uniqueSessionId);
         }
 
         if (matchingController == null) {
@@ -594,7 +558,7 @@
             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());
@@ -602,23 +566,23 @@
         }
 
         synchronized (sRouterLock) {
-            mSessionControllers.remove(uniqueSessionId, matchingController);
+            mRoutingControllers.remove(uniqueSessionId, matchingController);
         }
         matchingController.release();
         notifyControllerReleased(matchingController);
     }
 
     private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
-            RouteDiscoveryRequest discoveryRequest) {
+            RouteDiscoveryPreference discoveryRequest) {
         return routes.stream()
                 .filter(
-                        route -> route.containsRouteTypes(discoveryRequest.getRouteTypes()))
+                        route -> route.hasAnyFeatures(discoveryRequest.getPreferredFeatures()))
                 .collect(Collectors.toList());
     }
 
     private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
         for (RouteCallbackRecord record: mRouteCallbackRecords) {
-            List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest);
+            List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
             if (!filteredRoutes.isEmpty()) {
                 record.mExecutor.execute(
                         () -> record.mRouteCallback.onRoutesAdded(filteredRoutes));
@@ -628,7 +592,7 @@
 
     private void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
         for (RouteCallbackRecord record: mRouteCallbackRecords) {
-            List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest);
+            List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
             if (!filteredRoutes.isEmpty()) {
                 record.mExecutor.execute(
                         () -> record.mRouteCallback.onRoutesRemoved(filteredRoutes));
@@ -638,7 +602,7 @@
 
     private void notifyRoutesChanged(List<MediaRoute2Info> routes) {
         for (RouteCallbackRecord record: mRouteCallbackRecords) {
-            List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mRequest);
+            List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
             if (!filteredRoutes.isEmpty()) {
                 record.mExecutor.execute(
                         () -> record.mRouteCallback.onRoutesChanged(filteredRoutes));
@@ -646,22 +610,22 @@
         }
     }
 
-    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 routeType) {
+    private void notifySessionCreationFailed(MediaRoute2Info route, String routeFeature) {
         for (SessionCallbackRecord record: mSessionCallbackRecords) {
             record.mExecutor.execute(
-                    () -> record.mSessionCallback.onSessionCreationFailed(route, routeType));
+                    () -> 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(
@@ -669,7 +633,7 @@
         }
     }
 
-    private void notifyControllerReleased(RouteSessionController controller) {
+    private void notifyControllerReleased(RoutingController controller) {
         for (SessionCallbackRecord record: mSessionCallbackRecords) {
             record.mExecutor.execute(
                     () -> record.mSessionCallback.onSessionReleased(controller));
@@ -710,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 requestedRouteType the route type 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 requestedRouteType) {}
+                @NonNull String requestedRouteFeature) {}
 
         /**
          * Called when the session info has changed.
@@ -737,44 +702,45 @@
          * 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;
         }
 
@@ -788,17 +754,17 @@
         }
 
         /**
-         * @return the type of routes that the session includes.
+         * @return the feature which is used by the session mainly.
          */
         @NonNull
-        public String getRouteType() {
+        public String getRouteFeature() {
             synchronized (mControllerLock) {
-                return mSessionInfo.getRouteType();
+                return mSessionInfo.getRouteFeature();
             }
         }
 
         /**
-         * @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() {
@@ -1020,7 +986,7 @@
 
             Client2 client;
             synchronized (sRouterLock) {
-                mSessionControllers.remove(getSessionId(), this);
+                mRoutingControllers.remove(getSessionId(), this);
                 client = mClient;
             }
             if (client != null) {
@@ -1037,13 +1003,13 @@
          * @hide
          */
         @NonNull
-        public RouteSessionInfo getRouteSessionInfo() {
+        public RoutingSessionInfo getRoutingSessionInfo() {
             synchronized (mControllerLock) {
                 return mSessionInfo;
             }
         }
 
-        void setRouteSessionInfo(@NonNull RouteSessionInfo info) {
+        void setRoutingSessionInfo(@NonNull RoutingSessionInfo info) {
             synchronized (mControllerLock) {
                 mSessionInfo = info;
             }
@@ -1070,13 +1036,13 @@
     final class RouteCallbackRecord {
         public final Executor mExecutor;
         public final RouteCallback mRouteCallback;
-        public final RouteDiscoveryRequest mRequest;
+        public final RouteDiscoveryPreference mPreference;
 
         RouteCallbackRecord(@Nullable Executor executor, @NonNull RouteCallback routeCallback,
-                @Nullable RouteDiscoveryRequest request) {
+                @Nullable RouteDiscoveryPreference preference) {
             mRouteCallback = routeCallback;
             mExecutor = executor;
-            mRequest = request;
+            mPreference = preference;
         }
 
         @Override
@@ -1125,13 +1091,13 @@
 
     final class SessionCreationRequest {
         public final MediaRoute2Info mRoute;
-        public final String mRouteType;
+        public final String mRouteFeature;
         public final int mRequestId;
 
         SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route,
-                @NonNull String routeType) {
+                @NonNull String routeFeature) {
             mRoute = route;
-            mRouteType = routeType;
+            mRouteFeature = routeFeature;
             mRequestId = requestId;
         }
     }
@@ -1159,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 1e6ec51..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>> mRouteTypeMap = 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;
-                mRouteTypeMap.clear();
+                mPreferredFeaturesMap.clear();
             }
         }
     }
@@ -160,14 +160,14 @@
     public List<MediaRoute2Info> getAvailableRoutes(@NonNull String packageName) {
         Objects.requireNonNull(packageName, "packageName must not be null");
 
-        List<String> routeTypes = mRouteTypeMap.get(packageName);
-        if (routeTypes == 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.containsRouteTypes(routeTypes)) {
+                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;
@@ -352,15 +352,15 @@
         }
     }
 
-    void updateRouteTypes(String packageName, List<String> routeTypes) {
-        List<String> prevTypes = mRouteTypeMap.put(packageName, routeTypes);
-        if ((prevTypes == null && routeTypes.size() == 0)
-                || Objects.equals(routeTypes, prevTypes)) {
+    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, routeTypes));
+            record.mExecutor.execute(() -> record.mCallback
+                    .onControlCategoriesChanged(packageName, preferredFeatures));
         }
     }
 
@@ -398,13 +398,13 @@
 
 
         /**
-         * Called when the route types 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 routeTypes the list of route types 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> routeTypes) {}
+                @NonNull List<String> preferredFeatures) {}
     }
 
     final class CallbackRecord {
@@ -440,9 +440,9 @@
                     MediaRouter2Manager.this, packageName, route));
         }
 
-        public void notifyRouteTypesChanged(String packageName, List<String> routeTypes) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateRouteTypes,
-                    MediaRouter2Manager.this, packageName, routeTypes));
+        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/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/RouteDiscoveryRequest.aidl b/media/java/android/media/RouteDiscoveryRequest.aidl
deleted file mode 100644
index 744f656..0000000
--- a/media/java/android/media/RouteDiscoveryRequest.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-parcelable RouteDiscoveryRequest;
diff --git a/media/java/android/media/RouteDiscoveryRequest.java b/media/java/android/media/RouteDiscoveryRequest.java
deleted file mode 100644
index 88b31fb..0000000
--- a/media/java/android/media/RouteDiscoveryRequest.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.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;
-
-
-/**
- * @hide
- */
-public final class RouteDiscoveryRequest implements Parcelable {
-    @NonNull
-    public static final Creator<RouteDiscoveryRequest> CREATOR =
-            new Creator<RouteDiscoveryRequest>() {
-                @Override
-                public RouteDiscoveryRequest createFromParcel(Parcel in) {
-                    return new RouteDiscoveryRequest(in);
-                }
-
-                @Override
-                public RouteDiscoveryRequest[] newArray(int size) {
-                    return new RouteDiscoveryRequest[size];
-                }
-            };
-
-    @NonNull
-    private final List<String> mRouteTypes;
-    private final boolean mActiveScan;
-    @Nullable
-    private final Bundle mExtras;
-
-    /**
-     * @hide
-     */
-    public static final RouteDiscoveryRequest EMPTY =
-            new Builder(Collections.emptyList(), false).build();
-
-    RouteDiscoveryRequest(@NonNull Builder builder) {
-        mRouteTypes = builder.mRouteTypes;
-        mActiveScan = builder.mActiveScan;
-        mExtras = builder.mExtras;
-    }
-
-    RouteDiscoveryRequest(@NonNull Parcel in) {
-        mRouteTypes = in.createStringArrayList();
-        mActiveScan = in.readBoolean();
-        mExtras = in.readBundle();
-    }
-
-    @NonNull
-    public List<String> getRouteTypes() {
-        return mRouteTypes;
-    }
-
-    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(mRouteTypes);
-        dest.writeBoolean(mActiveScan);
-        dest.writeBundle(mExtras);
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder result = new StringBuilder()
-                .append("RouteDiscoveryRequest{ ")
-                .append("routeTypes={")
-                .append(String.join(", ", mRouteTypes))
-                .append("}")
-                .append(", activeScan=")
-                .append(mActiveScan)
-                .append(" }");
-
-        return result.toString();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof RouteDiscoveryRequest)) {
-            return false;
-        }
-        RouteDiscoveryRequest other = (RouteDiscoveryRequest) o;
-        return Objects.equals(mRouteTypes, other.mRouteTypes)
-                && mActiveScan == other.mActiveScan;
-    }
-
-    /**
-     * Builder for {@link RouteDiscoveryRequest}.
-     */
-    public static final class Builder {
-        List<String> mRouteTypes;
-        boolean mActiveScan;
-        Bundle mExtras;
-
-        public Builder(@NonNull List<String> routeTypes, boolean activeScan) {
-            mRouteTypes = new ArrayList<>(
-                    Objects.requireNonNull(routeTypes, "routeTypes must not be null"));
-            mActiveScan = activeScan;
-        }
-
-        public Builder(@NonNull RouteDiscoveryRequest request) {
-            Objects.requireNonNull(request, "request must not be null");
-
-            mRouteTypes = request.getRouteTypes();
-            mActiveScan = request.isActiveScan();
-            mExtras = request.getExtras();
-        }
-
-        /**
-         * A constructor to combine all of the requests into a single request.
-         * It ignores extras of requests.
-         */
-        Builder(@NonNull Collection<RouteDiscoveryRequest> requests) {
-            Set<String> routeTypeSet = new HashSet<>();
-            mActiveScan = false;
-            for (RouteDiscoveryRequest request : requests) {
-                routeTypeSet.addAll(request.mRouteTypes);
-                mActiveScan |= request.mActiveScan;
-            }
-            mRouteTypes = new ArrayList<>(routeTypeSet);
-        }
-
-        /**
-         * Sets route types to discover.
-         */
-        public Builder setRouteTypes(@NonNull List<String> routeTypes) {
-            mRouteTypes = new ArrayList<>(
-                    Objects.requireNonNull(routeTypes, "routeTypes must not be null"));
-            return this;
-        }
-
-        /**
-         * Sets if active scanning should be performed.
-         */
-        public Builder setActiveScan(boolean activeScan) {
-            mActiveScan = activeScan;
-            return this;
-        }
-
-        /**
-         * Sets the extras of the route.
-         * @hide
-         */
-        public Builder setExtras(@Nullable Bundle extras) {
-            mExtras = extras;
-            return this;
-        }
-
-        /**
-         * Builds the {@link RouteDiscoveryRequest}.
-         */
-        public RouteDiscoveryRequest build() {
-            return new RouteDiscoveryRequest(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/RouteSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
similarity index 65%
rename from media/java/android/media/RouteSessionInfo.java
rename to media/java/android/media/RoutingSessionInfo.java
index 5330630..96acf6c 100644
--- a/media/java/android/media/RouteSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -30,29 +30,28 @@
 import java.util.Objects;
 
 /**
- * Describes a route session that is made when a media route is selected.
+ * Describes a routing session which is created when a media route is selected.
  * @hide
  */
-public class RouteSessionInfo implements Parcelable {
-
+public final class RoutingSessionInfo implements Parcelable {
     @NonNull
-    public static final Creator<RouteSessionInfo> CREATOR =
-            new Creator<RouteSessionInfo>() {
+    public static final Creator<RoutingSessionInfo> CREATOR =
+            new Creator<RoutingSessionInfo>() {
                 @Override
-                public RouteSessionInfo createFromParcel(Parcel in) {
-                    return new RouteSessionInfo(in);
+                public RoutingSessionInfo createFromParcel(Parcel in) {
+                    return new RoutingSessionInfo(in);
                 }
                 @Override
-                public RouteSessionInfo[] newArray(int size) {
-                    return new RouteSessionInfo[size];
+                public RoutingSessionInfo[] newArray(int size) {
+                    return new RoutingSessionInfo[size];
                 }
             };
 
-    public static final String TAG = "RouteSessionInfo";
+    private static final String TAG = "RoutingSessionInfo";
 
     final String mId;
     final String mClientPackageName;
-    final String mRouteType;
+    final String mRouteFeature;
     @Nullable
     final String mProviderId;
     final List<String> mSelectedRoutes;
@@ -62,14 +61,15 @@
     @Nullable
     final Bundle mControlHints;
 
-    RouteSessionInfo(@NonNull Builder builder) {
+    RoutingSessionInfo(@NonNull Builder builder) {
         Objects.requireNonNull(builder, "builder must not be null.");
 
         mId = builder.mId;
         mClientPackageName = builder.mClientPackageName;
-        mRouteType = builder.mRouteType;
+        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(
@@ -82,12 +82,12 @@
         mControlHints = builder.mControlHints;
     }
 
-    RouteSessionInfo(@NonNull Parcel src) {
+    RoutingSessionInfo(@NonNull Parcel src) {
         Objects.requireNonNull(src, "src must not be null.");
 
         mId = ensureString(src.readString());
         mClientPackageName = ensureString(src.readString());
-        mRouteType = ensureString(src.readString());
+        mRouteFeature = ensureString(src.readString());
         mProviderId = src.readString();
 
         mSelectedRoutes = ensureList(src.createStringArrayList());
@@ -113,18 +113,6 @@
     }
 
     /**
-     * Returns whether the session info is valid or not
-     *
-     * TODO in this CL: Remove this method.
-     */
-    public boolean isValid() {
-        return !TextUtils.isEmpty(mId)
-                && !TextUtils.isEmpty(mClientPackageName)
-                && !TextUtils.isEmpty(mRouteType)
-                && mSelectedRoutes.size() > 0;
-    }
-
-    /**
      * Gets the id of the session. The sessions which are given by {@link MediaRouter2} will have
      * unique IDs.
      * <p>
@@ -160,12 +148,12 @@
     }
 
     /**
-     * Gets the route type of the session.
-     * Routes that don't have the type can't be added to the session.
+     * Gets the route feature of the session.
+     * Routes that don't have the feature can't be selected into the session.
      */
     @NonNull
-    public String getRouteType() {
-        return mRouteType;
+    public String getRouteFeature() {
+        return mRouteFeature;
     }
 
     /**
@@ -226,7 +214,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(mId);
         dest.writeString(mClientPackageName);
-        dest.writeString(mRouteType);
+        dest.writeString(mRouteFeature);
         dest.writeString(mProviderId);
         dest.writeStringList(mSelectedRoutes);
         dest.writeStringList(mSelectableRoutes);
@@ -236,11 +224,37 @@
     }
 
     @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("RouteSessionInfo{ ")
+                .append("RoutingSessionInfo{ ")
                 .append("sessionId=").append(mId)
-                .append(", routeType=").append(mRouteType)
+                .append(", routeFeature=").append(mRouteFeature)
                 .append(", selectedRoutes={")
                 .append(String.join(",", mSelectedRoutes))
                 .append("}")
@@ -276,12 +290,12 @@
     }
 
     /**
-     * Builder class for {@link RouteSessionInfo}.
+     * Builder class for {@link RoutingSessionInfo}.
      */
     public static final class Builder {
         final String mId;
         final String mClientPackageName;
-        final String mRouteType;
+        final String mRouteFeature;
         String mProviderId;
         final List<String> mSelectedRoutes;
         final List<String> mSelectableRoutes;
@@ -290,24 +304,32 @@
         Bundle mControlHints;
 
         /**
-         * Constructor for builder to create {@link RouteSessionInfo}.
+         * Constructor for builder to create {@link RoutingSessionInfo}.
          * <p>
          * In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of
-         * {@link RouteSessionInfo#getId()} can be different from what was set in
+         * {@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 routeType) {
+                @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 = Objects.requireNonNull(
-                    clientPackageName, "clientPackageName must not be null");
-            mRouteType = Objects.requireNonNull(routeType, "routeType must not be null");
+            mClientPackageName = clientPackageName;
+            mRouteFeature = routeFeature;
             mSelectedRoutes = new ArrayList<>();
             mSelectableRoutes = new ArrayList<>();
             mDeselectableRoutes = new ArrayList<>();
@@ -315,17 +337,17 @@
         }
 
         /**
-         * Constructor for builder to create {@link RouteSessionInfo} with
-         * existing {@link RouteSessionInfo} instance.
+         * Constructor for builder to create {@link RoutingSessionInfo} with
+         * existing {@link RoutingSessionInfo} instance.
          *
          * @param sessionInfo the existing instance to copy data from.
          */
-        public Builder(@NonNull RouteSessionInfo sessionInfo) {
+        public Builder(@NonNull RoutingSessionInfo sessionInfo) {
             Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
 
             mId = sessionInfo.mId;
             mClientPackageName = sessionInfo.mClientPackageName;
-            mRouteType = sessionInfo.mRouteType;
+            mRouteFeature = sessionInfo.mRouteFeature;
             mProviderId = sessionInfo.mProviderId;
 
             mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes);
@@ -338,8 +360,6 @@
 
         /**
          * Sets the provider ID of the session.
-         * Also, calling this method will make all type of route IDs be unique by adding
-         * {@code providerId:} as a prefix. So do NOT call this method twice on same instance.
          *
          * @hide
          */
@@ -362,20 +382,26 @@
         }
 
         /**
-         * Adds a route to the selected routes.
+         * Adds a route to the selected routes. The {@code routeId} must not be empty.
          */
         @NonNull
         public Builder addSelectedRoute(@NonNull String routeId) {
-            mSelectedRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mSelectedRoutes.add(routeId);
             return this;
         }
 
         /**
-         * Removes a route from the selected routes.
+         * Removes a route from the selected routes. The {@code routeId} must not be empty.
          */
         @NonNull
         public Builder removeSelectedRoute(@NonNull String routeId) {
-            mSelectedRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mSelectedRoutes.remove(routeId);
             return this;
         }
 
@@ -389,20 +415,26 @@
         }
 
         /**
-         * Adds a route to the selectable routes.
+         * Adds a route to the selectable routes. The {@code routeId} must not be empty.
          */
         @NonNull
         public Builder addSelectableRoute(@NonNull String routeId) {
-            mSelectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mSelectableRoutes.add(routeId);
             return this;
         }
 
         /**
-         * Removes a route from the selectable routes.
+         * Removes a route from the selectable routes. The {@code routeId} must not be empty.
          */
         @NonNull
         public Builder removeSelectableRoute(@NonNull String routeId) {
-            mSelectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mSelectableRoutes.remove(routeId);
             return this;
         }
 
@@ -416,20 +448,26 @@
         }
 
         /**
-         * Adds a route to the deselectable routes.
+         * Adds a route to the deselectable routes. The {@code routeId} must not be empty.
          */
         @NonNull
         public Builder addDeselectableRoute(@NonNull String routeId) {
-            mDeselectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mDeselectableRoutes.add(routeId);
             return this;
         }
 
         /**
-         * Removes a route from the deselectable routes.
+         * Removes a route from the deselectable routes. The {@code routeId} must not be empty.
          */
         @NonNull
         public Builder removeDeselectableRoute(@NonNull String routeId) {
-            mDeselectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mDeselectableRoutes.remove(routeId);
             return this;
         }
 
@@ -443,21 +481,26 @@
         }
 
         /**
-         * Adds a route to the transferrable routes.
+         * Adds a route to the transferrable routes. The {@code routeId} must not be empty.
          */
         @NonNull
         public Builder addTransferrableRoute(@NonNull String routeId) {
-            mTransferrableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mTransferrableRoutes.add(routeId);
             return this;
         }
 
         /**
-         * Removes a route from the transferrable routes.
+         * Removes a route from the transferrable routes. The {@code routeId} must not be empty.
          */
         @NonNull
         public Builder removeTransferrableRoute(@NonNull String routeId) {
-            mTransferrableRoutes.remove(
-                    Objects.requireNonNull(routeId, "routeId must not be null"));
+            if (TextUtils.isEmpty(routeId)) {
+                throw new IllegalArgumentException("routeId must not be empty");
+            }
+            mTransferrableRoutes.remove(routeId);
             return this;
         }
 
@@ -471,11 +514,16 @@
         }
 
         /**
-         * Builds a route session info.
+         * Builds a routing session info.
+         *
+         * @throws IllegalArgumentException if no selected routes are added.
          */
         @NonNull
-        public RouteSessionInfo build() {
-            return new RouteSessionInfo(this);
+        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/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 77596a5..118f65c 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -25,6 +25,7 @@
 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_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 909f1a00..9c56e7b 100644
--- a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
@@ -58,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/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index 4318a0a..5e0b1ea 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -318,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);
@@ -387,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];
                 }
@@ -444,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;
         }
@@ -454,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;
         }
@@ -479,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");
             }
@@ -494,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");
             }
@@ -570,7 +579,8 @@
          * @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");
             }
@@ -585,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");
             }
@@ -600,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");
             }
@@ -620,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");
             }
@@ -640,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");
             }
@@ -653,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;
         }
@@ -663,6 +679,7 @@
          *
          * @return The new {@link TvTrackInfo} instance
          */
+        @NonNull
         public TvTrackInfo build() {
             return new TvTrackInfo(mType, mId, mLanguage, mDescription, mEncrypted,
                     mAudioChannelCount, mAudioSampleRate, mAudioDescription, mHardOfHearing,
diff --git a/media/java/android/media/tv/tuner/Descrambler.java b/media/java/android/media/tv/tuner/Descrambler.java
index f9f7a22..0143582 100644
--- a/media/java/android/media/tv/tuner/Descrambler.java
+++ b/media/java/android/media/tv/tuner/Descrambler.java
@@ -16,9 +16,12 @@
 
 package android.media.tv.tuner;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.media.tv.tuner.Tuner.Filter;
-import android.media.tv.tuner.TunerConstants.DemuxPidType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * This class is used to interact with descramblers.
@@ -26,9 +29,25 @@
  * <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);
@@ -52,10 +71,8 @@
      * @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) {
+    public int addPid(@PidType int pidType, int pid, @Nullable Filter filter) {
         return nativeAddPid(pidType, pid, filter);
     }
 
@@ -68,10 +85,8 @@
      * @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) {
+    public int removePid(@PidType int pidType, int pid, @Nullable Filter filter) {
         return nativeRemovePid(pidType, pid, filter);
     }
 
@@ -83,17 +98,13 @@
      *
      * @param keyToken the token to be used to link the key slot.
      * @return result status of the operation.
-     *
-     * @hide
      */
-    public int setKeyToken(byte[] keyToken) {
+    public int setKeyToken(@Nullable byte[] keyToken) {
         return nativeSetKeyToken(keyToken);
     }
 
     /**
      * Release the descrambler instance.
-     *
-     * @hide
      */
     @Override
     public void close() {
diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java
index f181b49..c7cc9e6d 100644
--- a/media/java/android/media/tv/tuner/Lnb.java
+++ b/media/java/android/media/tv/tuner/Lnb.java
@@ -20,6 +20,7 @@
 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;
@@ -37,6 +38,7 @@
  *
  * @hide
  */
+@SystemApi
 public class Lnb implements AutoCloseable {
     /** @hide */
     @IntDef({VOLTAGE_NONE, VOLTAGE_5V, VOLTAGE_11V, VOLTAGE_12V, VOLTAGE_13V, VOLTAGE_14V,
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index 76c3f23..c2d6c58c 100644
--- a/media/java/android/media/tv/tuner/TunerConstants.java
+++ b/media/java/android/media/tv/tuner/TunerConstants.java
@@ -51,16 +51,6 @@
 
 
     /** @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({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,
@@ -1308,37 +1298,30 @@
 
     /**
      * Operation succeeded.
-     * @hide
      */
     public static final int RESULT_SUCCESS = Constants.Result.SUCCESS;
     /**
      * Operation failed because the corresponding resources are not available.
-     * @hide
      */
     public static final int RESULT_UNAVAILABLE = Constants.Result.UNAVAILABLE;
     /**
      * Operation failed because the corresponding resources are not initialized.
-     * @hide
      */
     public static final int RESULT_NOT_INITIALIZED = Constants.Result.NOT_INITIALIZED;
     /**
      * Operation failed because it's not in a valid state.
-     * @hide
      */
     public static final int RESULT_INVALID_STATE = Constants.Result.INVALID_STATE;
     /**
      * Operation failed because there are invalid arguments.
-     * @hide
      */
     public static final int RESULT_INVALID_ARGUMENT = Constants.Result.INVALID_ARGUMENT;
     /**
      * Memory allocation failed.
-     * @hide
      */
     public static final int RESULT_OUT_OF_MEMORY = Constants.Result.OUT_OF_MEMORY;
     /**
      * Operation failed due to unknown errors.
-     * @hide
      */
     public static final int RESULT_UNKNOWN_ERROR = Constants.Result.UNKNOWN_ERROR;
 
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 8c0273b..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_TYPE = "route_special_type";
-    public static final String ROUTE_NAME_SPECIAL_TYPE = "Special Type 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 TYPE_SAMPLE =
-            "com.android.mediarouteprovider.TYPE_SAMPLE";
-    public static final String TYPE_SPECIAL =
-            "com.android.mediarouteprovider.TYPE_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, String> 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)
-                .addRouteType(TYPE_SAMPLE)
-                .setDeviceType(DEVICE_TYPE_TV)
+                .addFeature(FEATURE_SAMPLE)
+                .setDeviceType(DEVICE_TYPE_REMOTE_TV)
                 .build();
         MediaRoute2Info route2 = new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2)
-                .addRouteType(TYPE_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)
-                .addRouteType(TYPE_SAMPLE)
+                .addFeature(FEATURE_SAMPLE)
                 .build();
         MediaRoute2Info route4 = new MediaRoute2Info.Builder(
                 ROUTE_ID4_TO_SELECT_AND_DESELECT, ROUTE_NAME4)
-                .addRouteType(TYPE_SAMPLE)
+                .addFeature(FEATURE_SAMPLE)
                 .build();
         MediaRoute2Info route5 = new MediaRoute2Info.Builder(
                 ROUTE_ID5_TO_TRANSFER_TO, ROUTE_NAME5)
-                .addRouteType(TYPE_SAMPLE)
+                .addFeature(FEATURE_SAMPLE)
                 .build();
         MediaRoute2Info routeSpecial =
-                new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_TYPE, ROUTE_NAME_SPECIAL_TYPE)
-                        .addRouteType(TYPE_SAMPLE)
-                        .addRouteType(TYPE_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)
-                        .addRouteType(TYPE_SAMPLE)
+                        .addFeature(FEATURE_SAMPLE)
                         .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_FIXED)
                         .build();
         MediaRoute2Info variableVolumeRoute =
                 new MediaRoute2Info.Builder(ROUTE_ID_VARIABLE_VOLUME, ROUTE_NAME_VARIABLE_VOLUME)
-                        .addRouteType(TYPE_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 routeType,
+    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)) {
@@ -183,10 +183,10 @@
         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, routeType)
+        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(String 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)
@@ -210,7 +210,7 @@
 
     @Override
     public void onSelectRoute(String sessionId, String routeId) {
-        RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
         MediaRoute2Info route = mRoutes.get(routeId);
         if (route == null || sessionInfo == null) {
             return;
@@ -220,9 +220,9 @@
         mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
                 .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)
@@ -233,18 +233,25 @@
 
     @Override
     public void onDeselectRoute(String sessionId, String routeId) {
-        RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+        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)
@@ -255,8 +262,8 @@
 
     @Override
     public void onTransferToRoute(String sessionId, String routeId) {
-        RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
-        RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+        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;
         }
 
-        String 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 ce4bb8e..007229a 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
@@ -18,23 +18,23 @@
 
 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.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_TYPE;
+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;
-import static com.android.mediaroutertest.MediaRouterManagerTest.TYPES_ALL;
-import static com.android.mediaroutertest.MediaRouterManagerTest.TYPES_SPECIAL;
-import static com.android.mediaroutertest.MediaRouterManagerTest.TYPE_SAMPLE;
-import static com.android.mediaroutertest.MediaRouterManagerTest.TYPE_SPECIAL;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -48,10 +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.RouteDiscoveryRequest;
-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;
@@ -100,10 +100,10 @@
      */
     @Test
     public void testGetRoutes() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(TYPES_SPECIAL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_SPECIAL);
 
         assertEquals(1, routes.size());
-        assertNotNull(routes.get(ROUTE_ID_SPECIAL_TYPE));
+        assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
     }
 
     @Test
@@ -115,9 +115,9 @@
                 .setIconUri(new Uri.Builder().path("icon").build())
                 .setVolume(5)
                 .setVolumeMax(20)
-                .addRouteType(TYPE_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();
@@ -138,21 +138,13 @@
                 .setClientPackageName("com.android.mediaroutertest")
                 .setConnectionState(CONNECTION_STATE_CONNECTING)
                 .setIconUri(new Uri.Builder().path("icon").build())
-                .addRouteType(TYPE_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);
@@ -170,7 +162,7 @@
         assertNotEquals(route, routeClient);
 
         MediaRoute2Info routeType = new MediaRoute2Info.Builder(route)
-                .addRouteType(TYPE_SPECIAL).build();
+                .addFeature(FEATURE_SPECIAL).build();
         assertNotEquals(route, routeType);
 
         MediaRoute2Info routeVolume = new MediaRoute2Info.Builder(route)
@@ -186,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(TYPES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_ALL);
 
         MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
         assertNotNull(volRoute);
@@ -204,13 +196,13 @@
                 () -> mRouter2.requestUpdateVolume(volRoute, deltaVolume),
                 ROUTE_ID_VARIABLE_VOLUME,
                 (route -> route.getVolume() == originalVolume + deltaVolume),
-                TYPES_ALL);
+                FEATURES_ALL);
 
         awaitOnRouteChanged(
                 () -> mRouter2.requestSetVolume(volRoute, originalVolume),
                 ROUTE_ID_VARIABLE_VOLUME,
                 (route -> route.getVolume() == originalVolume),
-                TYPES_ALL);
+                FEATURES_ALL);
     }
 
     @Test
@@ -237,13 +229,13 @@
     @Test
     public void testRequestCreateSessionWithInvalidArguments() {
         MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build();
-        String routeType = "routeType";
+        String routeFeature = "routeFeature";
 
         // Tests null route
         assertThrows(NullPointerException.class,
-                () -> mRouter2.requestCreateSession(null, routeType));
+                () -> mRouter2.requestCreateSession(null, routeFeature));
 
-        // Tests null or empty route type
+        // Tests null or empty route feature
         assertThrows(IllegalArgumentException.class,
                 () -> mRouter2.requestCreateSession(route, null));
         assertThrows(IllegalArgumentException.class,
@@ -252,42 +244,42 @@
 
     @Test
     public void testRequestCreateSessionSuccess() throws Exception {
-        final List<String> sampleRouteType = new ArrayList<>();
-        sampleRouteType.add(TYPE_SAMPLE);
+        final List<String> sampleRouteFeature = new ArrayList<>();
+        sampleRouteFeature.add(FEATURE_SAMPLE);
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
+        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(TYPE_SAMPLE, controller.getRouteType()));
+                assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature()));
                 controllers.add(controller);
                 successLatch.countDown();
             }
 
             @Override
             public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
-                    String requestedRouteType) {
+                    String requestedRouteFeature) {
                 failureLatch.countDown();
             }
         };
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
             mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(route, TYPE_SAMPLE);
+            mRouter2.requestCreateSession(route, FEATURE_SAMPLE);
             assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
             // onSessionCreationFailed should not be called.
@@ -302,7 +294,7 @@
     @Test
     public void testRequestCreateSessionFailure() throws Exception {
         final List<String> sampleRouteType = new ArrayList<>();
-        sampleRouteType.add(TYPE_SAMPLE);
+        sampleRouteType.add(FEATURE_SAMPLE);
 
         Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
         MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED);
@@ -310,32 +302,32 @@
 
         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 requestedRouteType) {
+                    String requestedRouteFeature) {
                 assertEquals(route, requestedRoute);
-                assertTrue(TextUtils.equals(TYPE_SAMPLE, requestedRouteType));
+                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, RouteDiscoveryRequest.EMPTY);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
             mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(route, TYPE_SAMPLE);
+            mRouter2.requestCreateSession(route, FEATURE_SAMPLE);
             assertTrue(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
             // onSessionCreated should not be called.
@@ -350,23 +342,23 @@
     @Test
     public void testRequestCreateSessionMultipleSessions() throws Exception {
         final List<String> sampleRouteType = new ArrayList<>();
-        sampleRouteType.add(TYPE_SAMPLE);
+        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 requestedRouteType) {
+                    String requestedRouteFeature) {
                 failureLatch.countDown();
             }
         };
@@ -379,12 +371,12 @@
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
             mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(route1, TYPE_SAMPLE);
-            mRouter2.requestCreateSession(route2, TYPE_SAMPLE);
+            mRouter2.requestCreateSession(route1, FEATURE_SAMPLE);
+            mRouter2.requestCreateSession(route2, FEATURE_SAMPLE);
             assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
 
             // onSessionCreationFailed should not be called.
@@ -392,14 +384,14 @@
 
             // 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(TYPE_SAMPLE, controller1.getRouteType()));
-            assertTrue(TextUtils.equals(TYPE_SAMPLE, controller2.getRouteType()));
+            assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller1.getRouteFeature()));
+            assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller2.getRouteFeature()));
 
         } finally {
             releaseControllers(createdControllers);
@@ -411,7 +403,7 @@
     @Test
     public void testSessionCallbackIsNotCalledAfterUnregistered() throws Exception {
         final List<String> sampleRouteType = new ArrayList<>();
-        sampleRouteType.add(TYPE_SAMPLE);
+        sampleRouteType.add(FEATURE_SAMPLE);
 
         Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
         MediaRoute2Info route = routes.get(ROUTE_ID1);
@@ -419,30 +411,30 @@
 
         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 requestedRouteType) {
+                    String requestedRouteFeature) {
                 failureLatch.countDown();
             }
         };
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
             mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(route, TYPE_SAMPLE);
+            mRouter2.requestCreateSession(route, FEATURE_SAMPLE);
 
             // Unregisters session callback
             mRouter2.unregisterSessionCallback(sessionCallback);
@@ -459,9 +451,9 @@
 
     // TODO: Add tests for illegal inputs if needed (e.g. selecting already selected route)
     @Test
-    public void testRouteSessionControllerSelectAndDeselectRoute() throws Exception {
+    public void testRoutingControllerSelectAndDeselectRoute() throws Exception {
         final List<String> sampleRouteType = new ArrayList<>();
-        sampleRouteType.add(TYPE_SAMPLE);
+        sampleRouteType.add(FEATURE_SAMPLE);
 
         Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
         MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
@@ -470,22 +462,22 @@
         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(TYPE_SAMPLE, controller.getRouteType()));
+                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
                         || !TextUtils.equals(
                                 controllers.get(0).getSessionId(), controller.getSessionId())) {
@@ -527,15 +519,15 @@
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
             mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_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));
 
@@ -558,9 +550,9 @@
     }
 
     @Test
-    public void testRouteSessionControllerTransferToRoute() throws Exception {
+    public void testRoutingControllerTransferToRoute() throws Exception {
         final List<String> sampleRouteType = new ArrayList<>();
-        sampleRouteType.add(TYPE_SAMPLE);
+        sampleRouteType.add(FEATURE_SAMPLE);
 
         Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
         MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
@@ -568,26 +560,22 @@
 
         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);
-                android.util.Log.d(TAG, "selected route ids ");
-                for (String routeId : getRouteIds(controller.getSelectedRoutes())) {
-                    android.util.Log.d(TAG, "route id : " + routeId);
-                }
                 assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
-                assertTrue(TextUtils.equals(TYPE_SAMPLE, controller.getRouteType()));
+                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
                         || !TextUtils.equals(
                                 controllers.get(0).getSessionId(), controller.getSessionId())) {
@@ -613,15 +601,15 @@
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
             mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_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));
 
@@ -641,9 +629,9 @@
     // TODO: Add tests for onSessionReleased() call.
 
     @Test
-    public void testRouteSessionControllerReleaseShouldIgnoreTransferTo() throws Exception {
+    public void testRoutingControllerReleaseShouldIgnoreTransferTo() throws Exception {
         final List<String> sampleRouteType = new ArrayList<>();
-        sampleRouteType.add(TYPE_SAMPLE);
+        sampleRouteType.add(FEATURE_SAMPLE);
 
         Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
         MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
@@ -651,22 +639,22 @@
 
         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(TYPE_SAMPLE, controller.getRouteType()));
+                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
                         || !TextUtils.equals(
                                 controllers.get(0).getSessionId(), controller.getSessionId())) {
@@ -678,15 +666,15 @@
 
         // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
         RouteCallback routeCallback = new RouteCallback();
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
 
         try {
             mRouter2.registerSessionCallback(mExecutor, sessionCallback);
-            mRouter2.requestCreateSession(routeToCreateSessionWith, TYPE_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));
 
@@ -735,7 +723,7 @@
         };
 
         mRouter2.registerRouteCallback(mExecutor, routeCallback,
-                new RouteDiscoveryRequest.Builder(routeTypes, true).build());
+                new RouteDiscoveryPreference.Builder(routeTypes, true).build());
         try {
             latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
             return createRouteMap(mRouter2.getRoutes());
@@ -744,8 +732,8 @@
         }
     }
 
-    static void releaseControllers(@NonNull List<RouteSessionController> controllers) {
-        for (RouteSessionController controller : controllers) {
+    static void releaseControllers(@NonNull List<RoutingController> controllers) {
+        for (RoutingController controller : controllers) {
             controller.release();
         }
     }
@@ -775,7 +763,7 @@
             }
         };
         mRouter2.registerRouteCallback(mExecutor, routeCallback,
-                new RouteDiscoveryRequest.Builder(routeTypes, true).build());
+                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 9ff9177c..ae15b91 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -31,7 +31,7 @@
 import android.media.MediaRouter2.RouteCallback;
 import android.media.MediaRouter2.SessionCallback;
 import android.media.MediaRouter2Manager;
-import android.media.RouteDiscoveryRequest;
+import android.media.RouteDiscoveryPreference;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -76,9 +76,9 @@
             SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id5_to_transfer_to";
     public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to";
 
-    public static final String ROUTE_ID_SPECIAL_TYPE =
-            SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_special_type";
-    public static final String ROUTE_NAME_SPECIAL_TYPE = "Special Type 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";
@@ -94,12 +94,12 @@
     public static final String ACTION_REMOVE_ROUTE =
             "com.android.mediarouteprovider.action_remove_route";
 
-    public static final String TYPE_SAMPLE =
-            "com.android.mediarouteprovider.TYPE_SAMPLE";
-    public static final String TYPE_SPECIAL =
-            "com.android.mediarouteprovider.TYPE_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 TYPE_LIVE_AUDIO = "android.media.intent.route.TYPE_LIVE_AUDIO";
+    private static final String FEATURE_LIVE_AUDIO = "android.media.intent.route.LIVE_AUDIO";
 
     private static final int TIMEOUT_MS = 5000;
 
@@ -113,18 +113,18 @@
     private final List<RouteCallback> mRouteCallbacks = new ArrayList<>();
     private final List<SessionCallback> mSessionCallbacks = new ArrayList<>();
 
-    public static final List<String> TYPES_ALL = new ArrayList();
-    public static final List<String> TYPES_SPECIAL = new ArrayList();
-    private static final List<String> TYPES_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 {
-        TYPES_ALL.add(TYPE_SAMPLE);
-        TYPES_ALL.add(TYPE_SPECIAL);
-        TYPES_ALL.add(TYPE_LIVE_AUDIO);
+        FEATURES_ALL.add(FEATURE_SAMPLE);
+        FEATURES_ALL.add(FEATURE_SPECIAL);
+        FEATURES_ALL.add(FEATURE_LIVE_AUDIO);
 
-        TYPES_SPECIAL.add(TYPE_SPECIAL);
+        FEATURES_SPECIAL.add(FEATURE_SPECIAL);
 
-        TYPES_LIVE_AUDIO.add(TYPE_LIVE_AUDIO);
+        FEATURES_LIVE_AUDIO.add(FEATURE_LIVE_AUDIO);
     }
 
     @Before
@@ -181,7 +181,7 @@
     @Test
     public void testOnRoutesRemoved() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
 
         addRouterCallback(new RouteCallback());
         addManagerCallback(new MediaRouter2Manager.Callback() {
@@ -203,14 +203,14 @@
     }
 
     /**
-     * Tests if we get proper routes for application that has special route type.
+     * Tests if we get proper routes for application that has special route feature.
      */
     @Test
-    public void testRouteType() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_SPECIAL);
+    public void testRouteFeatures() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_SPECIAL);
 
         assertEquals(1, routes.size());
-        assertNotNull(routes.get(ROUTE_ID_SPECIAL_TYPE));
+        assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
     }
 
     /**
@@ -219,7 +219,7 @@
      */
     @Test
     public void testRouterOnSessionCreated() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
 
         CountDownLatch latch = new CountDownLatch(1);
 
@@ -228,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();
                 }
@@ -255,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(TYPES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
 
         addRouterCallback(new RouteCallback());
         addManagerCallback(new MediaRouter2Manager.Callback() {
@@ -285,7 +285,7 @@
     public void testGetActiveRoutes() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
 
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
         addRouterCallback(new RouteCallback());
         addManagerCallback(new MediaRouter2Manager.Callback() {
             @Override
@@ -321,7 +321,7 @@
     @Test
     @Ignore("TODO: enable when session is released")
     public void testSingleProviderSelect() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
         addRouterCallback(new RouteCallback());
 
         awaitOnRouteChangedManager(
@@ -346,7 +346,7 @@
 
     @Test
     public void testControlVolumeWithManager() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
         MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
 
         int originalVolume = volRoute.getVolume();
@@ -365,7 +365,7 @@
 
     @Test
     public void testVolumeHandling() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(TYPES_ALL);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
 
         MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME);
         MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
@@ -375,11 +375,11 @@
         assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax());
     }
 
-    Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeTypes)
+    Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeFeatures)
             throws Exception {
         CountDownLatch latch = new CountDownLatch(2);
 
-        // A dummy callback is required to send route type info.
+        // A dummy callback is required to send route feature info.
         RouteCallback routeCallback = new RouteCallback();
         MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
             @Override
@@ -394,16 +394,17 @@
             }
 
             @Override
-            public void onControlCategoriesChanged(String packageName, List<String> routeTypes) {
+            public void onControlCategoriesChanged(String packageName,
+                    List<String> preferredFeatures) {
                 if (TextUtils.equals(mPackageName, packageName)
-                        && routeTypes.equals(routeTypes)) {
+                        && preferredFeatures.equals(preferredFeatures)) {
                     latch.countDown();
                 }
             }
         };
         mManager.registerCallback(mExecutor, managerCallback);
         mRouter2.registerRouteCallback(mExecutor, routeCallback,
-                new RouteDiscoveryRequest.Builder(routeTypes, true).build());
+                new RouteDiscoveryPreference.Builder(routeFeatures, true).build());
         try {
             latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
             return createRouteMap(mManager.getAvailableRoutes(mPackageName));
@@ -450,7 +451,7 @@
 
     private void addRouterCallback(RouteCallback routeCallback) {
         mRouteCallbacks.add(routeCallback);
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryRequest.EMPTY);
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
     }
 
     private void addSessionCallback(SessionCallback sessionCallback) {
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryRequestTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java
similarity index 75%
rename from media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryRequestTest.java
rename to media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java
index 60d131a..fa12935 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryRequestTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java
@@ -19,7 +19,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
-import android.media.RouteDiscoveryRequest;
+import android.media.RouteDiscoveryPreference;
 import android.os.Parcel;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -34,7 +34,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class RouteDiscoveryRequestTest {
+public class RouteDiscoveryPreferenceTest {
     @Before
     public void setUp() throws Exception { }
 
@@ -46,10 +46,10 @@
         List<String> testTypes = new ArrayList<>();
         testTypes.add("TEST_TYPE_1");
         testTypes.add("TEST_TYPE_2");
-        RouteDiscoveryRequest request = new RouteDiscoveryRequest.Builder(testTypes, true)
+        RouteDiscoveryPreference request = new RouteDiscoveryPreference.Builder(testTypes, true)
                 .build();
 
-        RouteDiscoveryRequest requestRebuilt = new RouteDiscoveryRequest.Builder(request)
+        RouteDiscoveryPreference requestRebuilt = new RouteDiscoveryPreference.Builder(request)
                 .build();
 
         assertEquals(request, requestRebuilt);
@@ -57,7 +57,7 @@
         Parcel parcel = Parcel.obtain();
         parcel.writeParcelable(request, 0);
         parcel.setDataPosition(0);
-        RouteDiscoveryRequest requestFromParcel = parcel.readParcelable(null);
+        RouteDiscoveryPreference requestFromParcel = parcel.readParcelable(null);
 
         assertEquals(request, requestFromParcel);
     }
@@ -71,15 +71,15 @@
         List<String> testTypes2 = new ArrayList<>();
         testTypes.add("TEST_TYPE_3");
 
-        RouteDiscoveryRequest request = new RouteDiscoveryRequest.Builder(testTypes, true)
+        RouteDiscoveryPreference request = new RouteDiscoveryPreference.Builder(testTypes, true)
                 .build();
 
-        RouteDiscoveryRequest requestTypes = new RouteDiscoveryRequest.Builder(request)
-                .setRouteTypes(testTypes2)
+        RouteDiscoveryPreference requestTypes = new RouteDiscoveryPreference.Builder(request)
+                .setPreferredFeatures(testTypes2)
                 .build();
         assertNotEquals(request, requestTypes);
 
-        RouteDiscoveryRequest requestActiveScan = new RouteDiscoveryRequest.Builder(request)
+        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 9971fc3..0000000
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-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_SESSION_ID = "test_session_id";
-    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(TEST_SESSION_ID,
-                "",
-                TEST_CONTROL_CATEGORY)
-                .addSelectedRoute(TEST_ROUTE_ID1)
-                .build();
-        RouteSessionInfo emptyCategorySession = new RouteSessionInfo.Builder(TEST_SESSION_ID,
-                TEST_PACKAGE_NAME, "")
-                .addSelectedRoute(TEST_ROUTE_ID1)
-                .build();
-
-        RouteSessionInfo emptySelectedRouteSession = new RouteSessionInfo.Builder(TEST_SESSION_ID,
-                TEST_PACKAGE_NAME, TEST_CONTROL_CATEGORY)
-                .build();
-
-        RouteSessionInfo validSession = new RouteSessionInfo.Builder(emptySelectedRouteSession)
-                .addSelectedRoute(TEST_ROUTE_ID1)
-                .build();
-
-        assertFalse(emptyPackageSession.isValid());
-        assertFalse(emptyCategorySession.isValid());
-        assertFalse(emptySelectedRouteSession.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/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/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/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/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/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/SystemUI/res/layout-land/global_actions_grid_item.xml b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
index bc12338..0f9deaa 100644
--- a/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
+++ b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
@@ -57,15 +57,5 @@
             android:textColor="@color/global_actions_text"
             android:textAppearance="?android:attr/textAppearanceSmall"
         />
-
-        <TextView
-            android:visibility="gone"
-            android:id="@*android:id/status"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:textColor="@color/global_actions_text"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-        />
     </LinearLayout>
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml
index 4404c874..31c7cbf 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_item.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml
@@ -56,15 +56,5 @@
             android:textColor="@color/global_actions_text"
             android:textAppearance="?android:attr/textAppearanceSmall"
         />
-
-        <TextView
-            android:visibility="gone"
-            android:id="@*android:id/status"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center"
-            android:textColor="@color/global_actions_text"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-        />
     </LinearLayout>
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_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/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/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/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index fc19fa0..91bb80c 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -1166,13 +1166,6 @@
             TextView messageView = (TextView) v.findViewById(R.id.message);
             messageView.setSelected(true); // necessary for marquee to work
 
-            TextView statusView = (TextView) v.findViewById(R.id.status);
-            final String status = getStatus();
-            if (!TextUtils.isEmpty(status)) {
-                statusView.setText(status);
-            } else {
-                statusView.setVisibility(View.GONE);
-            }
             if (mIcon != null) {
                 icon.setImageDrawable(mIcon);
                 icon.setScaleType(ScaleType.CENTER_CROP);
@@ -1257,32 +1250,26 @@
                 LayoutInflater inflater) {
             willCreate();
 
-            View v = inflater.inflate(R
-                    .layout.global_actions_item, parent, false);
+            View v = inflater.inflate(com.android.systemui.R
+                    .layout.global_actions_grid_item, parent, false);
 
             ImageView icon = (ImageView) v.findViewById(R.id.icon);
             TextView messageView = (TextView) v.findViewById(R.id.message);
-            TextView statusView = (TextView) v.findViewById(R.id.status);
             final boolean enabled = isEnabled();
+            boolean on = ((mState == State.On) || (mState == State.TurningOn));
 
             if (messageView != null) {
-                messageView.setText(mMessageResId);
+                messageView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
                 messageView.setEnabled(enabled);
                 messageView.setSelected(true); // necessary for marquee to work
             }
 
-            boolean on = ((mState == State.On) || (mState == State.TurningOn));
             if (icon != null) {
                 icon.setImageDrawable(context.getDrawable(
                         (on ? mEnabledIconResId : mDisabledIconResid)));
                 icon.setEnabled(enabled);
             }
 
-            if (statusView != null) {
-                statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
-                statusView.setVisibility(View.VISIBLE);
-                statusView.setEnabled(enabled);
-            }
             v.setEnabled(enabled);
 
             return v;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 02c4beb..9f2bbc6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -20,7 +20,7 @@
 import static android.view.View.VISIBLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED;
 import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;
 
 import android.animation.Animator;
@@ -246,20 +246,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 +271,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 +304,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 +355,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 +375,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 +462,7 @@
             }
 
             @Override
-            public void onAnimationEnd(android.animation.Animator animation) {
+            public void onAnimationEnd(Animator animation) {
                 mScreenshotFlash.setVisibility(View.GONE);
             }
         });
@@ -513,81 +483,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);
@@ -648,13 +543,16 @@
             });
             mActionsView.addView(actionChip);
         }
-        TextView scrollChip = (TextView) inflater.inflate(
-                R.layout.global_screenshot_action_chip, mActionsView, false);
-        Toast scrollNotImplemented = Toast.makeText(
-                mContext, "Not implemented", Toast.LENGTH_SHORT);
-        scrollChip.setText("Scroll"); // TODO (mkephart): add resource and translate
-        scrollChip.setOnClickListener(v -> scrollNotImplemented.show());
-        mActionsView.addView(scrollChip);
+
+        if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_SCROLLING_ENABLED, false)) {
+            TextView scrollChip = (TextView) inflater.inflate(
+                    R.layout.global_screenshot_action_chip, mActionsView, false);
+            Toast scrollNotImplemented = Toast.makeText(
+                    mContext, "Not implemented", Toast.LENGTH_SHORT);
+            scrollChip.setText("Scroll"); // TODO (mkephart): add resource and translate
+            scrollChip.setOnClickListener(v -> scrollNotImplemented.show());
+            mActionsView.addView(scrollChip);
+        }
 
         ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
         mActionsView.setY(mDisplayMetrics.heightPixels);
@@ -776,8 +674,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/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/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
index 6a2774b..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
@@ -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;
@@ -67,6 +67,7 @@
     private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
 
     private final Context mContext;
+    private final NotificationRowContentBinder mRowContentBinder;
     private final NotificationMessagingUtil mMessagingUtil;
     private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
             this::logNotificationExpansion;
@@ -79,7 +80,7 @@
     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;
@@ -90,6 +91,7 @@
             Context context,
             NotificationRemoteInputManager notificationRemoteInputManager,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
+            NotificationRowContentBinder rowContentBinder,
             @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
             KeyguardBypassController keyguardBypassController,
             StatusBarStateController statusBarStateController,
@@ -98,6 +100,7 @@
             NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             NotificationLogger logger) {
         mContext = context;
+        mRowContentBinder = rowContentBinder;
         mMessagingUtil = new NotificationMessagingUtil(context);
         mNotificationRemoteInputManager = notificationRemoteInputManager;
         mNotificationLockscreenUserManager = notificationLockscreenUserManager;
@@ -124,7 +127,7 @@
         mOnAppOpsClickListener = mGutsManager::openGuts;
     }
 
-    public void setInflationCallback(NotificationContentInflater.InflationCallback callback) {
+    public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) {
         mInflationCallback = callback;
     }
 
@@ -163,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);
-        mNotificationRemoteInputManager.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.
@@ -193,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);
     }
 
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 30f22ac..e1a6747 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
 
@@ -26,7 +27,6 @@
 import android.os.AsyncTask;
 import android.os.CancellationSignal;
 import android.service.notification.StatusBarNotification;
-import android.util.ArrayMap;
 import android.util.Log;
 import android.view.View;
 import android.widget.RemoteViews;
@@ -35,6 +35,7 @@
 import com.android.internal.widget.ImageMessageConsumer;
 import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.InflationTask;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.InflationException;
 import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
@@ -49,17 +50,30 @@
 
 import java.util.HashMap;
 
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
 /**
  * {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by
  * asynchronously building the content's {@link RemoteViews} and applying it to the row.
  */
+@Singleton
+@VisibleForTesting(visibility = PACKAGE)
 public class NotificationContentInflater implements NotificationRowContentBinder {
 
     public static final String TAG = "NotifContentInflater";
 
-    private RemoteViews.OnClickHandler mRemoteViewClickHandler;
     private boolean mInflateSynchronously = false;
-    private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>();
+    private final NotificationRemoteInputManager mRemoteInputManager;
+    private final NotifRemoteViewCache mRemoteViewCache;
+
+    @Inject
+    public NotificationContentInflater(
+            NotifRemoteViewCache remoteViewCache,
+            NotificationRemoteInputManager remoteInputManager) {
+        mRemoteViewCache = remoteViewCache;
+        mRemoteInputManager = remoteInputManager;
+    }
 
     @Override
     public void bindContent(
@@ -76,27 +90,27 @@
             return;
         }
 
-        StatusBarNotification sbn = row.getEntry().getSbn();
+        StatusBarNotification sbn = entry.getSbn();
 
         // To check if the notification has inline image and preload inline image if necessary.
         row.getImageResolver().preloadImages(sbn.getNotification());
 
         if (forceInflate) {
-            mCachedContentViews.clear();
+            mRemoteViewCache.clearCache(entry);
         }
 
         AsyncInflationTask task = new AsyncInflationTask(
-                sbn,
                 mInflateSynchronously,
                 contentToBind,
-                mCachedContentViews,
+                mRemoteViewCache,
+                entry,
                 row,
                 bindParams.isLowPriority,
                 bindParams.isChildInGroup,
                 bindParams.usesIncreasedHeight,
                 bindParams.usesIncreasedHeadsUpHeight,
                 callback,
-                mRemoteViewClickHandler);
+                mRemoteInputManager.getRemoteViewsOnClickHandler());
         if (mInflateSynchronously) {
             task.onPostExecute(task.doInBackground());
         } else {
@@ -123,13 +137,15 @@
         result = inflateSmartReplyViews(result, reInflateFlags, entry,
                 row.getContext(), packageContext, row.getHeadsUpManager(),
                 row.getExistingSmartRepliesAndActions());
+
         apply(
                 inflateSynchronously,
                 result,
                 reInflateFlags,
-                mCachedContentViews,
+                mRemoteViewCache,
+                entry,
                 row,
-                mRemoteViewClickHandler,
+                mRemoteInputManager.getRemoteViewsOnClickHandler(),
                 null);
         return result;
     }
@@ -149,7 +165,7 @@
         int curFlag = 1;
         while (contentToUnbind != 0) {
             if ((contentToUnbind & curFlag) != 0) {
-                freeNotificationView(row, curFlag);
+                freeNotificationView(entry, row, curFlag);
             }
             contentToUnbind &= ~curFlag;
             curFlag = curFlag << 1;
@@ -157,34 +173,25 @@
     }
 
     /**
-     * Set click handler for notification remote views
-     *
-     * @param remoteViewClickHandler click handler for remote views
-     */
-    public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
-        mRemoteViewClickHandler = remoteViewClickHandler;
-    }
-
-    /**
      * Frees the content view associated with the inflation flag.  Will only succeed if the
      * view is safe to remove.
      *
      * @param inflateFlag the flag corresponding to the content view which should be freed
      */
-    private void freeNotificationView(ExpandableNotificationRow row,
+    private void freeNotificationView(NotificationEntry entry, ExpandableNotificationRow row,
             @InflationFlag int inflateFlag) {
         switch (inflateFlag) {
             case FLAG_CONTENT_VIEW_HEADS_UP:
                 if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) {
                     row.getPrivateLayout().setHeadsUpChild(null);
-                    mCachedContentViews.remove(FLAG_CONTENT_VIEW_HEADS_UP);
+                    mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
                     row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null);
                 }
                 break;
             case FLAG_CONTENT_VIEW_PUBLIC:
                 if (row.getPublicLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) {
                     row.getPublicLayout().setContractedChild(null);
-                    mCachedContentViews.remove(FLAG_CONTENT_VIEW_PUBLIC);
+                    mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC);
                 }
                 break;
             case FLAG_CONTENT_VIEW_CONTRACTED:
@@ -245,11 +252,12 @@
         return result;
     }
 
-    public static CancellationSignal apply(
+    private static CancellationSignal apply(
             boolean inflateSynchronously,
             InflationProgress result,
             @InflationFlag int reInflateFlags,
-            ArrayMap<Integer, RemoteViews> cachedContentViews,
+            NotifRemoteViewCache remoteViewCache,
+            NotificationEntry entry,
             ExpandableNotificationRow row,
             RemoteViews.OnClickHandler remoteViewClickHandler,
             @Nullable InflationCallback callback) {
@@ -261,7 +269,7 @@
         if ((reInflateFlags & flag) != 0) {
             boolean isNewView =
                     !canReapplyRemoteView(result.newContentView,
-                            cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED));
+                            remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED));
             ApplyCallback applyCallback = new ApplyCallback() {
                 @Override
                 public void setResultView(View v) {
@@ -273,8 +281,8 @@
                     return result.newContentView;
                 }
             };
-            applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews,
-                    row, isNewView, remoteViewClickHandler, callback, privateLayout,
+            applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+                    entry, row, isNewView, remoteViewClickHandler, callback, privateLayout,
                     privateLayout.getContractedChild(), privateLayout.getVisibleWrapper(
                             NotificationContentView.VISIBLE_TYPE_CONTRACTED),
                     runningInflations, applyCallback);
@@ -285,7 +293,7 @@
             if (result.newExpandedView != null) {
                 boolean isNewView =
                         !canReapplyRemoteView(result.newExpandedView,
-                                cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED));
+                                remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED));
                 ApplyCallback applyCallback = new ApplyCallback() {
                     @Override
                     public void setResultView(View v) {
@@ -297,8 +305,8 @@
                         return result.newExpandedView;
                     }
                 };
-                applyRemoteView(inflateSynchronously, result, reInflateFlags, flag,
-                        cachedContentViews, row, isNewView, remoteViewClickHandler,
+                applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+                        entry, row, isNewView, remoteViewClickHandler,
                         callback, privateLayout, privateLayout.getExpandedChild(),
                         privateLayout.getVisibleWrapper(
                                 NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations,
@@ -311,7 +319,7 @@
             if (result.newHeadsUpView != null) {
                 boolean isNewView =
                         !canReapplyRemoteView(result.newHeadsUpView,
-                                cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP));
+                                remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP));
                 ApplyCallback applyCallback = new ApplyCallback() {
                     @Override
                     public void setResultView(View v) {
@@ -323,8 +331,8 @@
                         return result.newHeadsUpView;
                     }
                 };
-                applyRemoteView(inflateSynchronously, result, reInflateFlags, flag,
-                        cachedContentViews, row, isNewView, remoteViewClickHandler,
+                applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+                        entry, row, isNewView, remoteViewClickHandler,
                         callback, privateLayout, privateLayout.getHeadsUpChild(),
                         privateLayout.getVisibleWrapper(
                                 VISIBLE_TYPE_HEADSUP), runningInflations,
@@ -336,7 +344,7 @@
         if ((reInflateFlags & flag) != 0) {
             boolean isNewView =
                     !canReapplyRemoteView(result.newPublicView,
-                            cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC));
+                            remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC));
             ApplyCallback applyCallback = new ApplyCallback() {
                 @Override
                 public void setResultView(View v) {
@@ -348,15 +356,16 @@
                     return result.newPublicView;
                 }
             };
-            applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews,
-                    row, isNewView, remoteViewClickHandler, callback,
+            applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+                    entry, row, isNewView, remoteViewClickHandler, callback,
                     publicLayout, publicLayout.getContractedChild(),
                     publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED),
                     runningInflations, applyCallback);
         }
 
         // Let's try to finish, maybe nobody is even inflating anything
-        finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations, callback, row);
+        finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, callback, entry,
+                row);
         CancellationSignal cancellationSignal = new CancellationSignal();
         cancellationSignal.setOnCancelListener(
                 () -> runningInflations.values().forEach(CancellationSignal::cancel));
@@ -369,7 +378,8 @@
             final InflationProgress result,
             final @InflationFlag int reInflateFlags,
             @InflationFlag int inflationId,
-            final ArrayMap<Integer, RemoteViews> cachedContentViews,
+            final NotifRemoteViewCache remoteViewCache,
+            final NotificationEntry entry,
             final ExpandableNotificationRow row,
             boolean isNewView,
             RemoteViews.OnClickHandler remoteViewClickHandler,
@@ -422,8 +432,8 @@
                     existingWrapper.onReinflated();
                 }
                 runningInflations.remove(inflationId);
-                finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations,
-                        callback, row);
+                finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations,
+                        callback, entry, row);
             }
 
             @Override
@@ -488,11 +498,11 @@
      * @return true if the inflation was finished
      */
     private static boolean finishIfDone(InflationProgress result,
-            @InflationFlag int reInflateFlags, ArrayMap<Integer, RemoteViews> cachedContentViews,
+            @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache,
             HashMap<Integer, CancellationSignal> runningInflations,
-            @Nullable InflationCallback endListener, ExpandableNotificationRow row) {
+            @Nullable InflationCallback endListener, NotificationEntry entry,
+            ExpandableNotificationRow row) {
         Assert.isMainThread();
-        NotificationEntry entry = row.getEntry();
         NotificationContentView privateLayout = row.getPrivateLayout();
         NotificationContentView publicLayout = row.getPublicLayout();
         if (runningInflations.isEmpty()) {
@@ -500,23 +510,27 @@
                 if (result.inflatedContentView != null) {
                     // New view case
                     privateLayout.setContractedChild(result.inflatedContentView);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView);
-                } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED) != null) {
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+                            result.newContentView);
+                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) {
                     // Reinflation case. Only update if it's still cached (i.e. view has not been
                     // freed while inflating).
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView);
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+                            result.newContentView);
                 }
             }
 
             if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
                 if (result.inflatedExpandedView != null) {
                     privateLayout.setExpandedChild(result.inflatedExpandedView);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView);
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+                            result.newExpandedView);
                 } else if (result.newExpandedView == null) {
                     privateLayout.setExpandedChild(null);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, null);
-                } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED) != null) {
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView);
+                    remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
+                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) {
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+                            result.newExpandedView);
                 }
                 if (result.newExpandedView != null) {
                     privateLayout.setExpandedInflatedSmartReplies(
@@ -530,12 +544,14 @@
             if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
                 if (result.inflatedHeadsUpView != null) {
                     privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView);
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+                            result.newHeadsUpView);
                 } else if (result.newHeadsUpView == null) {
                     privateLayout.setHeadsUpChild(null);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, null);
-                } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP) != null) {
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView);
+                    remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
+                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) {
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+                            result.newHeadsUpView);
                 }
                 if (result.newHeadsUpView != null) {
                     privateLayout.setHeadsUpInflatedSmartReplies(
@@ -548,16 +564,18 @@
             if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
                 if (result.inflatedPublicView != null) {
                     publicLayout.setContractedChild(result.inflatedPublicView);
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView);
-                } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC) != null) {
-                    cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView);
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+                            result.newPublicView);
+                } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) {
+                    remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+                            result.newPublicView);
                 }
             }
 
             entry.headsUpStatusBarText = result.headsUpStatusBarText;
             entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
             if (endListener != null) {
-                endListener.onAsyncInflationFinished(row.getEntry(), reInflateFlags);
+                endListener.onAsyncInflationFinished(entry, reInflateFlags);
             }
             return true;
         }
@@ -615,7 +633,7 @@
     public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
             implements InflationCallback, InflationTask {
 
-        private final StatusBarNotification mSbn;
+        private final NotificationEntry mEntry;
         private final Context mContext;
         private final boolean mInflateSynchronously;
         private final boolean mIsLowPriority;
@@ -624,17 +642,17 @@
         private final InflationCallback mCallback;
         private final boolean mUsesIncreasedHeadsUpHeight;
         private @InflationFlag int mReInflateFlags;
-        private final ArrayMap<Integer, RemoteViews> mCachedContentViews;
+        private final NotifRemoteViewCache mRemoteViewCache;
         private ExpandableNotificationRow mRow;
         private Exception mError;
         private RemoteViews.OnClickHandler mRemoteViewClickHandler;
         private CancellationSignal mCancellationSignal;
 
         private AsyncInflationTask(
-                StatusBarNotification notification,
                 boolean inflateSynchronously,
                 @InflationFlag int reInflateFlags,
-                ArrayMap<Integer, RemoteViews> cachedContentViews,
+                NotifRemoteViewCache cache,
+                NotificationEntry entry,
                 ExpandableNotificationRow row,
                 boolean isLowPriority,
                 boolean isChildInGroup,
@@ -642,11 +660,11 @@
                 boolean usesIncreasedHeadsUpHeight,
                 InflationCallback callback,
                 RemoteViews.OnClickHandler remoteViewClickHandler) {
+            mEntry = entry;
             mRow = row;
-            mSbn = notification;
             mInflateSynchronously = inflateSynchronously;
             mReInflateFlags = reInflateFlags;
-            mCachedContentViews = cachedContentViews;
+            mRemoteViewCache = cache;
             mContext = mRow.getContext();
             mIsLowPriority = isLowPriority;
             mIsChildInGroup = isChildInGroup;
@@ -654,7 +672,6 @@
             mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight;
             mRemoteViewClickHandler = remoteViewClickHandler;
             mCallback = callback;
-            NotificationEntry entry = row.getEntry();
             entry.setInflationTask(this);
         }
 
@@ -667,12 +684,13 @@
         @Override
         protected InflationProgress doInBackground(Void... params) {
             try {
+                final StatusBarNotification sbn = mEntry.getSbn();
                 final Notification.Builder recoveredBuilder
                         = Notification.Builder.recoverBuilder(mContext,
-                        mSbn.getNotification());
+                        sbn.getNotification());
 
-                Context packageContext = mSbn.getPackageContext(mContext);
-                Notification notification = mSbn.getNotification();
+                Context packageContext = sbn.getPackageContext(mContext);
+                Notification notification = sbn.getNotification();
                 if (notification.isMediaNotification()) {
                     MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
                             packageContext);
@@ -681,7 +699,7 @@
                 InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
                         recoveredBuilder, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight,
                         mUsesIncreasedHeadsUpHeight, packageContext);
-                return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(),
+                return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry,
                         mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
                         mRow.getExistingSmartRepliesAndActions());
             } catch (Exception e) {
@@ -694,15 +712,15 @@
         protected void onPostExecute(InflationProgress result) {
             if (mError == null) {
                 mCancellationSignal = apply(mInflateSynchronously, result, mReInflateFlags,
-                        mCachedContentViews, mRow, mRemoteViewClickHandler, this);
+                        mRemoteViewCache, mEntry, mRow, mRemoteViewClickHandler, this);
             } else {
                 handleError(mError);
             }
         }
 
         private void handleError(Exception e) {
-            mRow.getEntry().onInflationTaskFinished();
-            StatusBarNotification sbn = mRow.getEntry().getSbn();
+            mEntry.onInflationTaskFinished();
+            StatusBarNotification sbn = mEntry.getSbn();
             final String ident = sbn.getPackageName() + "/0x"
                     + Integer.toHexString(sbn.getId());
             Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
@@ -736,10 +754,10 @@
         @Override
         public void onAsyncInflationFinished(NotificationEntry entry,
                 @InflationFlag int inflatedFlags) {
-            mRow.getEntry().onInflationTaskFinished();
+            mEntry.onInflationTaskFinished();
             mRow.onNotificationUpdated();
             if (mCallback != null) {
-                mCallback.onAsyncInflationFinished(mRow.getEntry(), inflatedFlags);
+                mCallback.onAsyncInflationFinished(mEntry, inflatedFlags);
             }
 
             // Notify the resolver that the inflation task has finished,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/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/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/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/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 77659df..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,7 +359,6 @@
 
     private static void inflateAndWait(ExpandableNotificationRow row) throws Exception {
         CountDownLatch countDownLatch = new CountDownLatch(1);
-        row.getNotificationInflater().setInflateSynchronously(true);
         NotificationContentInflater.InflationCallback callback =
                 new NotificationContentInflater.InflationCallback() {
                     @Override
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 a60fd52..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
@@ -84,7 +84,10 @@
 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;
@@ -200,9 +203,15 @@
 
         mEntry.expandedIcon = mock(StatusBarIconView.class);
 
+        NotificationRowContentBinder contentBinder = new NotificationContentInflater(
+                mock(NotifRemoteViewCache.class),
+                mRemoteInputManager);
+
         NotificationRowBinderImpl notificationRowBinder =
                 new NotificationRowBinderImpl(mContext,
-                        mRemoteInputManager, mLockscreenUserManager,
+                        mRemoteInputManager,
+                        mLockscreenUserManager,
+                        contentBinder,
                         true, /* allowLongPress */
                         mock(KeyguardBypassController.class),
                         mock(StatusBarStateController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
new file mode 100644
index 0000000..d7214f3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.widget.RemoteViews;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class NotifRemoteViewCacheImplTest extends SysuiTestCase {
+
+    private NotifRemoteViewCacheImpl mNotifRemoteViewCache;
+    private NotificationEntry mEntry;
+    private NotificationEntryListener mEntryListener;
+    @Mock private RemoteViews mRemoteViews;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mEntry = new NotificationEntryBuilder().build();
+
+        NotificationEntryManager entryManager = mock(NotificationEntryManager.class);
+        mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(entryManager);
+        ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
+                ArgumentCaptor.forClass(NotificationEntryListener.class);
+        verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture());
+        mEntryListener = entryListenerCaptor.getValue();
+    }
+
+    @Test
+    public void testPutCachedView() {
+        // GIVEN an initialized cache for an entry.
+        mEntryListener.onPendingEntryAdded(mEntry);
+
+        // WHEN a notification's cached remote views is put in.
+        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+
+        // THEN the remote view is cached.
+        assertTrue(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+        assertEquals(
+                "Cached remote view is not the one we put in.",
+                mRemoteViews,
+                mNotifRemoteViewCache.getCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+    }
+
+    @Test
+    public void testRemoveCachedView() {
+        // GIVEN a cache with a cached view.
+        mEntryListener.onPendingEntryAdded(mEntry);
+        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+
+        // WHEN we remove the cached view.
+        mNotifRemoteViewCache.removeCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED);
+
+        // THEN the remote view is not cached.
+        assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+    }
+
+    @Test
+    public void testClearCache() {
+        // GIVEN a non-empty cache.
+        mEntryListener.onPendingEntryAdded(mEntry);
+        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED, mRemoteViews);
+
+        // WHEN we clear the cache.
+        mNotifRemoteViewCache.clearCache(mEntry);
+
+        // THEN the cache is empty.
+        assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+        assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index f916fe5..cb9da6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -22,11 +22,16 @@
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.Notification;
 import android.content.Context;
@@ -35,16 +40,17 @@
 import android.os.Looper;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
-import android.util.ArrayMap;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.RemoteViews;
+import android.widget.TextView;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.filters.Suppress;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.InflationTask;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
@@ -57,6 +63,8 @@
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.HashMap;
 import java.util.concurrent.CountDownLatch;
@@ -73,8 +81,11 @@
     private Notification.Builder mBuilder;
     private ExpandableNotificationRow mRow;
 
+    @Mock private NotifRemoteViewCache mCache;
+
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
         mBuilder = new Notification.Builder(mContext).setSmallIcon(
                 R.drawable.ic_person)
                 .setContentTitle("Title")
@@ -83,7 +94,9 @@
         ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
                 mBuilder.build());
         mRow = spy(row);
-        mNotificationInflater = new NotificationContentInflater();
+        mNotificationInflater = new NotificationContentInflater(
+                mCache,
+                mock(NotificationRemoteInputManager.class));
     }
 
     @Test
@@ -174,7 +187,9 @@
                 result,
                 FLAG_CONTENT_VIEW_EXPANDED,
                 0,
-                new ArrayMap() /* cachedContentViews */, mRow,
+                mock(NotifRemoteViewCache.class),
+                mRow.getEntry(),
+                mRow,
                 true /* isNewView */, (v, p, r) -> true,
                 new InflationCallback() {
                     @Override
@@ -244,6 +259,71 @@
                 NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView));
     }
 
+    @Test
+    public void testUsesSameViewWhenCachedPossibleToReuse() throws Exception {
+        // GIVEN a cached view.
+        RemoteViews contractedRemoteView = mBuilder.createContentView();
+        when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+                .thenReturn(true);
+        when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+                .thenReturn(contractedRemoteView);
+
+        // GIVEN existing bound view with same layout id.
+        View view = contractedRemoteView.apply(mContext, null /* parent */);
+        mRow.getPrivateLayout().setContractedChild(view);
+
+        // WHEN inflater inflates
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+        // THEN the view should be re-used
+        assertEquals("Binder inflated a new view even though the old one was cached and usable.",
+                view, mRow.getPrivateLayout().getContractedChild());
+    }
+
+    @Test
+    public void testInflatesNewViewWhenCachedNotPossibleToReuse() throws Exception {
+        // GIVEN a cached remote view.
+        RemoteViews contractedRemoteView = mBuilder.createHeadsUpContentView();
+        when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+                .thenReturn(true);
+        when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+                .thenReturn(contractedRemoteView);
+
+        // GIVEN existing bound view with different layout id.
+        View view = new TextView(mContext);
+        mRow.getPrivateLayout().setContractedChild(view);
+
+        // WHEN inflater inflates
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+        // THEN the view should be a new view
+        assertNotEquals("Binder (somehow) used the same view when inflating.",
+                view, mRow.getPrivateLayout().getContractedChild());
+    }
+
+    @Test
+    public void testInflationCachesCreatedRemoteView() throws Exception {
+        // WHEN inflater inflates
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+        // THEN inflater informs cache of the new remote view
+        verify(mCache).putCachedView(
+                eq(mRow.getEntry()),
+                eq(FLAG_CONTENT_VIEW_CONTRACTED),
+                any());
+    }
+
+    @Test
+    public void testUnbindRemovesCachedRemoteView() {
+        // WHEN inflated unbinds content
+        mNotificationInflater.unbindContent(mRow.getEntry(), mRow, FLAG_CONTENT_VIEW_HEADS_UP);
+
+        // THEN inflated informs cache to remove remote view
+        verify(mCache).removeCachedView(
+                eq(mRow.getEntry()),
+                eq(FLAG_CONTENT_VIEW_HEADS_UP));
+    }
+
     private static void inflateAndWait(NotificationContentInflater inflater,
             @InflationFlag int contentToInflate,
             ExpandableNotificationRow row)
diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml
index e99c2c5..5a71eb2 100644
--- a/packages/Tethering/AndroidManifest.xml
+++ b/packages/Tethering/AndroidManifest.xml
@@ -32,6 +32,7 @@
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
     <uses-permission android:name="android.permission.MANAGE_USB" />
     <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
     <uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
     <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml
index 37e679d..ca290c6 100644
--- a/packages/Tethering/res/values/config.xml
+++ b/packages/Tethering/res/values/config.xml
@@ -1,7 +1,152 @@
 <?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>
+
+    <!-- Use the old dnsmasq DHCP server for tethering instead of the framework implementation. -->
+    <bool translatable="false" name="config_tether_enable_legacy_dhcp_server">false</bool>
+
+    <!-- Dhcp range (min, max) to use for tethering purposes -->
+    <string-array translatable="false" name="config_tether_dhcp_range">
+    </string-array>
+
+    <!-- Array of ConnectivityManager.TYPE_{BLUETOOTH, ETHERNET, MOBILE, MOBILE_DUN, MOBILE_HIPRI,
+         WIFI} values allowable for tethering.
+
+         Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
+         [1,7,0] for TYPE_WIFI, TYPE_BLUETOOTH, and TYPE_MOBILE.
+
+         This list is also modified by code within the framework, including:
+
+             - TYPE_ETHERNET (9) is prepended to this list, and
+
+             - the return value of TelephonyManager.isTetheringApnRequired()
+               determines how the array is further modified:
+
+                   * TRUE (DUN REQUIRED).
+                     TYPE_MOBILE is removed (if present).
+                     TYPE_MOBILE_HIPRI is removed (if present).
+                     TYPE_MOBILE_DUN is appended (if not already present).
+
+                   * FALSE (DUN NOT REQUIRED).
+                     TYPE_MOBILE_DUN is removed (if present).
+                     If both of TYPE_MOBILE{,_HIPRI} are not present:
+                        TYPE_MOBILE is appended.
+                        TYPE_MOBILE_HIPRI is appended.
+
+         For other changes applied to this list, now and in the future, see
+         com.android.server.connectivity.tethering.TetheringConfiguration.
+
+         Note also: the order of this is important. The first upstream type
+         for which a satisfying network exists is used.
+    -->
+    <integer-array translatable="false" name="config_tether_upstream_types">
+    </integer-array>
+
+    <!-- When true, the tethering upstream network follows the current default
+         Internet network (except when the current default network is mobile,
+         in which case a DUN network will be used if required).
+
+         When true, overrides the config_tether_upstream_types setting above.
+    -->
+    <bool translatable="false" name="config_tether_upstream_automatic">true</bool>
+
+
+    <!-- If the mobile hotspot feature requires provisioning, a package name and class name
+         can be provided to launch a supported application that provisions the devices.
+         EntitlementManager will send an 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..e089d9d
--- /dev/null
+++ b/packages/Tethering/res/values/overlayable.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <overlayable name="TetheringConfig">
+        <policy type="product|system|vendor">
+            <item type="array" name="config_tether_usb_regexs"/>
+            <item type="array" name="config_tether_wifi_regexs"/>
+            <item type="array" name="config_tether_wifi_p2p_regexs"/>
+            <item type="array" name="config_tether_bluetooth_regexs"/>
+            <item type="array" name="config_tether_dhcp_range"/>
+            <item type="bool" name="config_tether_enable_legacy_dhcp_server"/>
+            <item type="array" name="config_tether_upstream_types"/>
+            <item type="bool" name="config_tether_upstream_automatic"/>
+            <!-- Configuration values for tethering entitlement check -->
+            <item type="array" name="config_mobile_hotspot_provision_app"/>
+            <item type="string" name="config_mobile_hotspot_provision_app_no_ui"/>
+            <item type="string" name="config_mobile_hotspot_provision_response"/>
+            <item type="integer" name="config_mobile_hotspot_provision_check_period"/>
+            <item type="string" name="config_wifi_tether_enable"/>
+        </policy>
+    </overlayable>
+</resources>
diff --git a/packages/Tethering/res/values/strings.xml b/packages/Tethering/res/values/strings.xml
index ca866a9..792bce9 100644
--- a/packages/Tethering/res/values/strings.xml
+++ b/packages/Tethering/res/values/strings.xml
@@ -1,4 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
 <resources>
     <!-- Shown when the device is tethered -->
     <!-- Strings for tethered notification title [CHAR LIMIT=200] -->
@@ -9,8 +23,11 @@
     <!-- This notification is shown when tethering has been disabled on a user's device.
     The device is managed by the user's employer. Tethering can't be turned on unless the
     IT administrator allows it. The noun "admin" is another reference for "IT administrator." -->
-    <!-- Strings for tether disabling notification title  [CHAR LIMIT=200] -->
+    <!-- Strings for tether disabling notification title [CHAR LIMIT=200] -->
     <string name="disable_tether_notification_title">Tethering is disabled</string>
-    <!-- Strings for tether disabling notification message  [CHAR LIMIT=200] -->
+    <!-- Strings for tether disabling notification message [CHAR LIMIT=200] -->
     <string name="disable_tether_notification_message">Contact your admin for details</string>
+
+    <!-- Strings for tether notification channel name [CHAR LIMIT=200] -->
+    <string name="notification_channel_tethering_status">Hotspot &amp; tethering status</string>
 </resources>
\ No newline at end of file
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index d6abfb9..038d7ae 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -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;
@@ -106,8 +107,6 @@
 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;
@@ -663,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();
         }
@@ -688,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;
         }
 
@@ -719,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;
@@ -735,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;
         }
     }
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 397ba8a..dbe7892 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -21,7 +21,7 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
-import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 
 import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
 import static com.android.internal.R.array.config_tether_bluetooth_regexs;
@@ -33,13 +33,13 @@
 import static com.android.internal.R.bool.config_tether_upstream_automatic;
 import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period;
 import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui;
+import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.TetheringConfigurationParcel;
 import android.net.util.SharedLog;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -84,6 +84,12 @@
 
     private static final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
 
+    /**
+     * Use the old dnsmasq DHCP server for tethering instead of the framework implementation.
+     */
+    public static final String TETHER_ENABLE_LEGACY_DHCP_SERVER =
+            "tether_enable_legacy_dhcp_server";
+
     public final String[] tetherableUsbRegexs;
     public final String[] tetherableWifiRegexs;
     public final String[] tetherableWifiP2pRegexs;
@@ -122,7 +128,7 @@
 
         legacyDhcpRanges = getLegacyDhcpRanges(res);
         defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
-        enableLegacyDhcpServer = getEnableLegacyDhcpServer(ctx);
+        enableLegacyDhcpServer = getEnableLegacyDhcpServer(res);
 
         provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app);
         provisioningAppNoUi = getProvisioningAppNoUi(res);
@@ -332,10 +338,14 @@
         }
     }
 
-    private static boolean getEnableLegacyDhcpServer(Context ctx) {
-        final ContentResolver cr = ctx.getContentResolver();
-        final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
-        return intVal != 0;
+    private boolean getEnableLegacyDhcpServer(final Resources res) {
+        return getResourceBoolean(res, config_tether_enable_legacy_dhcp_server)
+                || getDeviceConfigBoolean(TETHER_ENABLE_LEGACY_DHCP_SERVER);
+    }
+
+    @VisibleForTesting
+    protected boolean getDeviceConfigBoolean(final String name) {
+        return DeviceConfig.getBoolean(NAMESPACE_CONNECTIVITY, name, false /** defaultValue */);
     }
 
     private Resources getResources(Context ctx, int subId) {
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 5692a6f..2875f71 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -23,6 +23,8 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
 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;
 
@@ -215,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(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.
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 66eba9a..79bba7f 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
@@ -22,10 +22,12 @@
 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.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -39,7 +41,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.util.SharedLog;
@@ -49,9 +50,8 @@
 import android.os.ResultReceiver;
 import android.os.SystemProperties;
 import android.os.test.TestLooper;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 import android.telephony.CarrierConfigManager;
-import android.test.mock.MockContentResolver;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -60,7 +60,6 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.test.BroadcastInterceptingContext;
-import com.android.internal.util.test.FakeSettingsProvider;
 
 import org.junit.After;
 import org.junit.Before;
@@ -94,7 +93,6 @@
     private final PersistableBundle mCarrierConfig = new PersistableBundle();
     private final TestLooper mLooper = new TestLooper();
     private Context mMockContext;
-    private MockContentResolver mContentResolver;
 
     private TestStateMachine mSM;
     private WrappedEntitlementManager mEnMgr;
@@ -110,11 +108,6 @@
         public Resources getResources() {
             return mResources;
         }
-
-        @Override
-        public ContentResolver getContentResolver() {
-            return mContentResolver;
-        }
     }
 
     public class WrappedEntitlementManager extends EntitlementManager {
@@ -151,13 +144,17 @@
         MockitoAnnotations.initMocks(this);
         mMockingSession = mockitoSession()
                 .initMocks(this)
-                .spyStatic(SystemProperties.class)
+                .mockStatic(SystemProperties.class)
+                .mockStatic(DeviceConfig.class)
                 .strictness(Strictness.WARN)
                 .startMocking();
         // Don't disable tethering provisioning unless requested.
         doReturn(false).when(
                 () -> SystemProperties.getBoolean(
                 eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY), anyBoolean()));
+        doReturn(false).when(
+                () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
 
         when(mResources.getStringArray(R.array.config_tether_dhcp_range))
             .thenReturn(new String[0]);
@@ -169,10 +166,9 @@
             .thenReturn(new String[0]);
         when(mResources.getIntArray(R.array.config_tether_upstream_types))
             .thenReturn(new int[0]);
+        when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
         when(mLog.forSubComponent(anyString())).thenReturn(mLog);
 
-        mContentResolver = new MockContentResolver();
-        mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         mMockContext = new MockContext(mContext);
         mSM = new TestStateMachine();
         mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE);
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
index 7799da4..ef97ad4 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
@@ -21,40 +21,44 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
 import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
 import static com.android.internal.R.array.config_tether_bluetooth_regexs;
 import static com.android.internal.R.array.config_tether_dhcp_range;
 import static com.android.internal.R.array.config_tether_upstream_types;
 import static com.android.internal.R.array.config_tether_usb_regexs;
 import static com.android.internal.R.array.config_tether_wifi_regexs;
+import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.when;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.util.SharedLog;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
 import android.telephony.TelephonyManager;
-import android.test.mock.MockContentResolver;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.test.BroadcastInterceptingContext;
-import com.android.internal.util.test.FakeSettingsProvider;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
 
 import java.util.Arrays;
 import java.util.Iterator;
@@ -69,9 +73,10 @@
     @Mock private TelephonyManager mTelephonyManager;
     @Mock private Resources mResources;
     @Mock private Resources mResourcesForSubId;
-    private MockContentResolver mContentResolver;
     private Context mMockContext;
     private boolean mHasTelephonyManager;
+    private boolean mEnableLegacyDhcpServer;
+    private MockitoSession mMockingSession;
 
     private class MockTetheringConfiguration extends TetheringConfiguration {
         MockTetheringConfiguration(Context ctx, SharedLog log, int id) {
@@ -101,16 +106,20 @@
             }
             return super.getSystemService(name);
         }
-
-        @Override
-        public ContentResolver getContentResolver() {
-            return mContentResolver;
-        }
     }
 
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        // TODO: use a dependencies class instead of mock statics.
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(DeviceConfig.class)
+                .strictness(Strictness.WARN)
+                .startMocking();
+        doReturn(false).when(
+                () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
+
         when(mResources.getStringArray(config_tether_dhcp_range)).thenReturn(new String[0]);
         when(mResources.getStringArray(config_tether_usb_regexs)).thenReturn(new String[0]);
         when(mResources.getStringArray(config_tether_wifi_regexs))
@@ -119,10 +128,15 @@
         when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]);
         when(mResources.getStringArray(config_mobile_hotspot_provision_app))
                 .thenReturn(new String[0]);
-        mContentResolver = new MockContentResolver();
-        mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
         mHasTelephonyManager = true;
         mMockContext = new MockContext(mContext);
+        mEnableLegacyDhcpServer = false;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mMockingSession.finishMocking();
     }
 
     private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) {
@@ -268,19 +282,35 @@
 
     @Test
     public void testNewDhcpServerDisabled() {
-        Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1);
+        when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(true);
+        doReturn(false).when(
+                () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
 
-        final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
-        assertTrue(cfg.enableLegacyDhcpServer);
+        final TetheringConfiguration enableByRes =
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        assertTrue(enableByRes.enableLegacyDhcpServer);
+
+        when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
+        doReturn(true).when(
+                () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
+
+        final TetheringConfiguration enableByDevConfig =
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        assertTrue(enableByDevConfig.enableLegacyDhcpServer);
     }
 
     @Test
     public void testNewDhcpServerEnabled() {
-        Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
+        when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
+        doReturn(false).when(
+                () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+                eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
 
-        final TetheringConfiguration cfg = new TetheringConfiguration(
-                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        final TetheringConfiguration cfg =
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+
         assertFalse(cfg.enableLegacyDhcpServer);
     }
 
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 7af48a8..e1fe3bf 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
@@ -35,9 +35,10 @@
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
-import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
+import static com.android.networkstack.tethering.R.bool.config_tether_enable_legacy_dhcp_server;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -224,6 +225,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 {
@@ -265,6 +271,11 @@
         }
 
         @Override
+        protected boolean getDeviceConfigBoolean(final String name) {
+            return false;
+        }
+
+        @Override
         protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) {
             return mResources;
         }
@@ -423,6 +434,7 @@
                 .thenReturn(new int[0]);
         when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic))
                 .thenReturn(false);
+        when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(false);
         when(mNetd.interfaceGetList())
                 .thenReturn(new String[] {
                         TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME});
@@ -432,9 +444,9 @@
                 .thenReturn(true);
 
         mServiceContext = new TestContext(mContext);
+        when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(null);
         mContentResolver = new MockContentResolver(mServiceContext);
         mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
-        Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
         mIntents = new Vector<>();
         mBroadcastReceiver = new BroadcastReceiver() {
             @Override
@@ -686,7 +698,7 @@
 
     @Test
     public void workingMobileUsbTethering_IPv4LegacyDhcp() {
-        Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1);
+        when(mResources.getBoolean(config_tether_enable_legacy_dhcp_server)).thenReturn(true);
         sendConfigurationChanged();
         final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
         runUsbTethering(upstreamState);
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/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/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index d45a54e..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);
         }
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/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 76572d3..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.
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 73b6c7a..7be3d11 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -2079,8 +2079,9 @@
         @Override
         public long currentNetworkTimeMillis() {
             final NtpTrustedTime time = NtpTrustedTime.getInstance(getContext());
-            if (time.hasCache()) {
-                return time.currentTimeMillis();
+            NtpTrustedTime.TimeResult ntpResult = time.getCachedTimeResult();
+            if (ntpResult != null) {
+                return ntpResult.currentTimeMillis();
             } else {
                 throw new ParcelableException(new DateTimeException("Missing NTP fix"));
             }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index c2e32d3..26c12c1 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -84,12 +84,12 @@
 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;
@@ -364,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
@@ -403,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
@@ -2394,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();
@@ -2631,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
@@ -2640,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: {
@@ -2672,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.
@@ -2685,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;
                 }
@@ -2727,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);
@@ -2744,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);
@@ -2803,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;
@@ -2842,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:
@@ -3031,16 +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
-                NetworkFactoryInfo nfi = mNetworkFactoryInfos.get(msg.replyTo);
-                nfi.completeConnection();
-                sendAllRequestsToFactory(nfi);
+                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) {
@@ -3072,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);
         }
     }
 
@@ -3156,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);
@@ -3169,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));
@@ -3419,8 +3421,8 @@
                 }
             }
 
-            for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
-                nfi.cancelRequest(nri.request);
+            for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+                npi.cancelRequest(nri.request);
             }
         } else {
             // listens don't have a singular affectedNetwork.  Check all networks to see
@@ -3470,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);
         }
@@ -3516,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.
@@ -3727,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);
@@ -3760,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;
         }
 
@@ -3855,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: {
@@ -4905,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;
@@ -4913,18 +4916,18 @@
     @GuardedBy("mUidToNetworkRequestCount")
     private final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray();
 
-    private static class NetworkFactoryInfo {
+    private static class NetworkProviderInfo {
         public final String name;
         public final Messenger messenger;
         private final AsyncChannel mAsyncChannel;
         private final IBinder.DeathRecipient mDeathRecipient;
-        public final int factorySerialNumber;
+        public final int providerId;
 
-        NetworkFactoryInfo(String name, Messenger messenger, AsyncChannel asyncChannel,
-                int factorySerialNumber, IBinder.DeathRecipient deathRecipient) {
+        NetworkProviderInfo(String name, Messenger messenger, AsyncChannel asyncChannel,
+                int providerId, IBinder.DeathRecipient deathRecipient) {
             this.name = name;
             this.messenger = messenger;
-            this.factorySerialNumber = factorySerialNumber;
+            this.providerId = providerId;
             mAsyncChannel = asyncChannel;
             mDeathRecipient = deathRecipient;
 
@@ -4942,17 +4945,17 @@
                 messenger.send(Message.obtain(null /* handler */, what, arg1, arg2, obj));
             } catch (RemoteException e) {
                 // Remote process died. Ignore; the death recipient will remove this
-                // NetworkFactoryInfo from mNetworkFactoryInfos.
+                // NetworkProviderInfo from mNetworkProviderInfos.
             }
         }
 
-        void requestNetwork(NetworkRequest request, int score, int servingSerialNumber) {
+        void requestNetwork(NetworkRequest request, int score, int servingProviderId) {
             if (isLegacyNetworkFactory()) {
                 mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score,
-                        servingSerialNumber, request);
+                        servingProviderId, request);
             } else {
                 sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score,
-                            servingSerialNumber, request);
+                            servingProviderId, request);
             }
         }
 
@@ -5365,45 +5368,45 @@
     @Override
     public int registerNetworkFactory(Messenger messenger, String name) {
         enforceNetworkFactoryPermission();
-        NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel(),
+        NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, new AsyncChannel(),
                 nextNetworkProviderId(), null /* deathRecipient */);
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi));
-        return nfi.factorySerialNumber;
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi));
+        return npi.providerId;
     }
 
-    private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) {
-        if (mNetworkFactoryInfos.containsKey(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 NetworkFactoryInfo "
-                    + mNetworkFactoryInfos.get(nfi.messenger).name);
+            Slog.e(TAG, "Attempt to register existing NetworkProviderInfo "
+                    + mNetworkProviderInfos.get(npi.messenger).name);
             return;
         }
 
-        if (DBG) log("Got NetworkFactory Messenger for " + nfi.name);
-        mNetworkFactoryInfos.put(nfi.messenger, nfi);
-        nfi.connect(mContext, mTrackerHandler);
-        if (!nfi.isLegacyNetworkFactory()) {
+        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.
-            sendAllRequestsToFactory(nfi);
+            sendAllRequestsToProvider(npi);
         }
     }
 
     @Override
     public int registerNetworkProvider(Messenger messenger, String name) {
         enforceNetworkFactoryPermission();
-        NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger,
+        NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger,
                 null /* asyncChannel */, nextNetworkProviderId(),
                 () -> unregisterNetworkProvider(messenger));
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi));
-        return nfi.factorySerialNumber;
+        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_FACTORY, messenger));
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger));
     }
 
     @Override
@@ -5411,13 +5414,13 @@
         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
@@ -5489,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);
     }
 
     /**
@@ -5506,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);
@@ -5523,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();
@@ -5803,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() &&
@@ -5945,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());
@@ -6043,13 +6046,13 @@
         if (VDBG || DDBG){
             log("sending new Min Network Score(" + score + "): " + networkRequest.toString());
         }
-        for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
-            nfi.requestNetwork(networkRequest, score, serial);
+        for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+            npi.requestNetwork(networkRequest, score, serial);
         }
     }
 
     /** Sends all current NetworkRequests to the specified factory. */
-    private void sendAllRequestsToFactory(NetworkFactoryInfo nfi) {
+    private void sendAllRequestsToProvider(NetworkProviderInfo npi) {
         ensureRunningOnConnectivityServiceThread();
         for (NetworkRequestInfo nri : mNetworkRequests.values()) {
             if (nri.request.isListen()) continue;
@@ -6061,9 +6064,9 @@
                 serial = nai.factorySerialNumber;
             } else {
                 score = 0;
-                serial = NetworkFactory.SerialNumber.NONE;
+                serial = NetworkProvider.ID_NONE;
             }
-            nfi.requestNetwork(nri.request, score, serial);
+            npi.requestNetwork(nri.request, score, serial);
         }
     }
 
@@ -6344,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;
@@ -6377,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
@@ -6636,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/NetworkTimeUpdateServiceImpl.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
index d20936c..7894788 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
@@ -154,17 +154,20 @@
 
     private void onPollNetworkTimeUnderWakeLock(int event) {
         // Force an NTP fix when outdated
-        if (mTime.getCacheAge() >= mPollingIntervalMs) {
+        NtpTrustedTime.TimeResult cachedNtpResult = mTime.getCachedTimeResult();
+        if (cachedNtpResult == null || cachedNtpResult.getAgeMillis() >= mPollingIntervalMs) {
             if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh");
             mTime.forceRefresh();
+            cachedNtpResult = mTime.getCachedTimeResult();
         }
 
-        if (mTime.getCacheAge() < mPollingIntervalMs) {
+        if (cachedNtpResult != null && cachedNtpResult.getAgeMillis() < mPollingIntervalMs) {
             // Obtained fresh fix; schedule next normal update
             resetAlarm(mPollingIntervalMs);
 
             // Suggest the time to the time detector. It may choose use it to set the system clock.
-            TimestampedValue<Long> timeSignal = mTime.getCachedNtpTimeSignal();
+            TimestampedValue<Long> timeSignal = new TimestampedValue<>(
+                    cachedNtpResult.getElapsedRealtimeMillis(), cachedNtpResult.getTimeMillis());
             NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal);
             timeSuggestion.addDebugInfo("Origin: NetworkTimeUpdateServiceImpl. event=" + event);
             mTimeDetector.suggestNetworkTime(timeSuggestion);
@@ -275,8 +278,11 @@
         TimeUtils.formatDuration(mPollingIntervalShorterMs, pw);
         pw.println("\nTryAgainTimesMax: " + mTryAgainTimesMax);
         pw.println("\nTryAgainCounter: " + mTryAgainCounter);
-        pw.println("NTP cache age: " + mTime.getCacheAge());
-        pw.println("NTP cache certainty: " + mTime.getCacheCertainty());
+        NtpTrustedTime.TimeResult ntpResult = mTime.getCachedTimeResult();
+        pw.println("NTP cache result: " + ntpResult);
+        if (ntpResult != null) {
+            pw.println("NTP result age: " + ntpResult.getAgeMillis());
+        }
         pw.println();
     }
 }
diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
index 190fff1..21fa9f9 100644
--- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
+++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
@@ -46,4 +46,7 @@
 
     /** Update the OEM unlock enabled bit, bypassing user restriction checks. */
     void forceOemUnlockEnabled(boolean enabled);
+
+    /** Retrieves the UID that can access the persistent data partition. */
+    int getAllowedUid();
 }
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 73c8520..00d8b0f 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -680,6 +680,11 @@
             writeDataBuffer(getTestHarnessModeDataOffset(), ByteBuffer.allocate(size));
         }
 
+        @Override
+        public int getAllowedUid() {
+            return mAllowedUid;
+        }
+
         private void writeInternal(byte[] data, long offset, int dataLength) {
             checkArgument(data == null || data.length > 0, "data must be null or non-empty");
             checkArgument(
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index c5409f85..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/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b9f2e76..e11008c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1562,8 +1562,7 @@
                     throw e.rethrowAsRuntimeException();
                 }
                 int numGids = 3;
-                if (mountExternal == Zygote.MOUNT_EXTERNAL_INSTALLER
-                        || mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) {
+                if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) {
                     numGids++;
                 }
                 /*
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/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/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/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/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
index f964d4c..d3588d3 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
@@ -48,6 +48,7 @@
 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;
@@ -82,28 +83,28 @@
             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);
         }
@@ -126,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());
@@ -220,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,
@@ -244,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/NtpTimeHelper.java b/services/core/java/com/android/server/location/NtpTimeHelper.java
index 67841ac..d2296ea 100644
--- a/services/core/java/com/android/server/location/NtpTimeHelper.java
+++ b/services/core/java/com/android/server/location/NtpTimeHelper.java
@@ -130,7 +130,8 @@
 
         // force refresh NTP cache when outdated
         boolean refreshSuccess = true;
-        if (mNtpTime.getCacheAge() >= NTP_INTERVAL) {
+        NtpTrustedTime.TimeResult ntpResult = mNtpTime.getCachedTimeResult();
+        if (ntpResult == null || ntpResult.getAgeMillis() >= NTP_INTERVAL) {
             // Blocking network operation.
             refreshSuccess = mNtpTime.forceRefresh();
         }
@@ -140,17 +141,17 @@
 
             // only update when NTP time is fresh
             // If refreshSuccess is false, cacheAge does not drop down.
-            if (mNtpTime.getCacheAge() < NTP_INTERVAL) {
-                long time = mNtpTime.getCachedNtpTime();
-                long timeReference = mNtpTime.getCachedNtpTimeReference();
-                long certainty = mNtpTime.getCacheCertainty();
+            ntpResult = mNtpTime.getCachedTimeResult();
+            if (ntpResult != null && ntpResult.getAgeMillis() < NTP_INTERVAL) {
+                long time = ntpResult.getTimeMillis();
+                long timeReference = ntpResult.getElapsedRealtimeMillis();
+                long certainty = ntpResult.getCertaintyMillis();
 
                 if (DEBUG) {
                     long now = System.currentTimeMillis();
                     Log.d(TAG, "NTP server returned: "
-                            + time + " (" + new Date(time)
-                            + ") reference: " + timeReference
-                            + " certainty: " + certainty
+                            + time + " (" + new Date(time) + ")"
+                            + " ntpResult: " + ntpResult
                             + " system time offset: " + (time - now));
                 }
 
diff --git a/services/core/java/com/android/server/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 9c9a412..0d315cd 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -21,7 +21,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.media.MediaRoute2ProviderInfo;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -34,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.");
@@ -68,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 {
@@ -81,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);
@@ -103,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 6359835..c0ad46f 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -26,7 +26,7 @@
 import android.media.IMediaRoute2ProviderClient;
 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;
@@ -270,7 +270,7 @@
     }
 
     private void onProviderStateUpdated(Connection connection,
-            MediaRoute2ProviderInfo providerInfo, List<RouteSessionInfo> sessionInfos) {
+            MediaRoute2ProviderInfo providerInfo, List<RoutingSessionInfo> sessionInfos) {
         if (mActiveConnection != connection) {
             return;
         }
@@ -280,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,7 +303,7 @@
             return;
         }
 
-        sessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+        sessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
                 .setProviderId(getUniqueId())
                 .build();
 
@@ -422,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));
         }
     }
@@ -450,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);
@@ -458,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);
@@ -466,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 487ab52..c48c90d 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -31,8 +31,8 @@
 import android.media.IMediaRouter2Manager;
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
-import android.media.RouteDiscoveryRequest;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -178,18 +178,18 @@
     }
 
     public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route,
-            String routeType, 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(routeType)) {
-            throw new IllegalArgumentException("routeType 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, routeType, requestId);
+                requestCreateSessionLocked(client, route, routeFeature, requestId);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -284,15 +284,15 @@
     }
 
     public void setDiscoveryRequest2(@NonNull IMediaRouter2Client client,
-            @NonNull RouteDiscoveryRequest request) {
+            @NonNull RouteDiscoveryPreference preference) {
         Objects.requireNonNull(client, "client must not be null");
-        Objects.requireNonNull(request, "request 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());
-                setDiscoveryRequestLocked(clientRecord, request);
+                setDiscoveryRequestLocked(clientRecord, preference);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -370,7 +370,7 @@
     }
 
     @NonNull
-    public List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
+    public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
@@ -450,7 +450,7 @@
     }
 
     private void requestCreateSessionLocked(@NonNull IMediaRouter2Client client,
-            @NonNull MediaRoute2Info route, @NonNull String routeType, long requestId) {
+            @NonNull MediaRoute2Info route, @NonNull String routeFeature, long requestId) {
         final IBinder binder = client.asBinder();
         final Client2Record clientRecord = mAllClientRecords.get(binder);
 
@@ -463,7 +463,7 @@
             clientRecord.mUserRecord.mHandler.sendMessage(
                     obtainMessage(UserHandler::requestCreateSessionOnHandler,
                             clientRecord.mUserRecord.mHandler,
-                            clientRecord, route, routeType, requestId));
+                            clientRecord, route, routeFeature, requestId));
         }
     }
 
@@ -519,13 +519,13 @@
     }
 
     private void setDiscoveryRequestLocked(Client2Record clientRecord,
-            RouteDiscoveryRequest discoveryRequest) {
+            RouteDiscoveryPreference discoveryRequest) {
         if (clientRecord != null) {
-            if (clientRecord.mDiscoveryRequest.equals(discoveryRequest)) {
+            if (clientRecord.mDiscoveryPreference.equals(discoveryRequest)) {
                 return;
             }
 
-            clientRecord.mDiscoveryRequest = discoveryRequest;
+            clientRecord.mDiscoveryPreference = discoveryRequest;
             clientRecord.mUserRecord.mHandler.sendMessage(
                     obtainMessage(UserHandler::updateClientUsage,
                             clientRecord.mUserRecord.mHandler, clientRecord));
@@ -622,9 +622,9 @@
             }
             long uniqueRequestId = toUniqueRequestId(managerRecord.mClientId, requestId);
             if (clientRecord != null && managerRecord.mTrusted) {
-                //TODO: select route type properly
+                //TODO: select route feature properly
                 requestCreateSessionLocked(clientRecord.mClient, route,
-                        route.getRouteTypes().get(0), uniqueRequestId);
+                        route.getFeatures().get(0), uniqueRequestId);
             }
         }
     }
@@ -653,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);
 
@@ -661,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());
         }
@@ -742,7 +742,7 @@
         public final boolean mTrusted;
         public final int mClientId;
 
-        public RouteDiscoveryRequest mDiscoveryRequest;
+        public RouteDiscoveryPreference mDiscoveryPreference;
         public boolean mIsManagerSelecting;
         public MediaRoute2Info mSelectingRoute;
         public MediaRoute2Info mSelectedRoute;
@@ -752,7 +752,7 @@
             mUserRecord = userRecord;
             mPackageName = packageName;
             mSelectRouteSequenceNumbers = new ArrayList<>();
-            mDiscoveryRequest = RouteDiscoveryRequest.EMPTY;
+            mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
             mClient = client;
             mUid = uid;
             mPid = pid;
@@ -876,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));
         }
@@ -932,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)) {
@@ -978,7 +979,7 @@
         }
 
         private void requestCreateSessionOnHandler(Client2Record clientRecord,
-                MediaRoute2Info route, String routeType, long requestId) {
+                MediaRoute2Info route, String routeFeature, long requestId) {
 
             final MediaRoute2Provider provider = findProvider(route.getProviderId());
             if (provider == null) {
@@ -988,20 +989,20 @@
                 return;
             }
 
-            if (!route.getRouteTypes().contains(routeType)) {
+            if (!route.getFeatures().contains(routeFeature)) {
                 Slog.w(TAG, "Ignoring session creation request since the given route=" + route
-                        + " doesn't support the given type=" + routeType);
+                        + " 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, routeType, requestId);
+                    clientRecord, route, routeFeature, requestId);
             mSessionCreationRequests.add(request);
 
             provider.requestCreateSession(clientRecord.mPackageName, route.getOriginalId(),
-                    routeType, requestId);
+                    routeFeature, requestId);
         }
 
         private void selectRouteOnHandler(@NonNull Client2Record clientRecord,
@@ -1131,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) {
@@ -1159,16 +1160,16 @@
             }
 
             String originalRouteId = matchingRequest.mRoute.getId();
-            String originalRouteType = matchingRequest.mRouteType;
+            String originalRouteFeature = matchingRequest.mRouteFeature;
             Client2Record client2Record = matchingRequest.mClientRecord;
 
             if (!sessionInfo.getSelectedRoutes().contains(originalRouteId)
-                    || !TextUtils.equals(originalRouteType,
-                        sessionInfo.getRouteType())) {
+                    || !TextUtils.equals(originalRouteFeature,
+                        sessionInfo.getRouteFeature())) {
                 Slog.w(TAG, "Created session doesn't match the original request."
                         + " originalRouteId=" + originalRouteId
-                        + ", originalRouteType=" + originalRouteType + ", requestId=" + requestId
-                        + ", sessionInfo=" + sessionInfo);
+                        + ", originalRouteFeature=" + originalRouteFeature
+                        + ", requestId=" + requestId + ", sessionInfo=" + sessionInfo);
                 notifySessionCreationFailed(matchingRequest.mClientRecord,
                         toClientRequestId(requestId));
                 return;
@@ -1182,7 +1183,7 @@
         }
 
         private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
-                @NonNull RouteSessionInfo sessionInfo) {
+                @NonNull RoutingSessionInfo sessionInfo) {
 
             Client2Record client2Record = mSessionToClientMap.get(
                     sessionInfo.getId());
@@ -1196,7 +1197,7 @@
         }
 
         private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
-                @NonNull RouteSessionInfo sessionInfo) {
+                @NonNull RoutingSessionInfo sessionInfo) {
 
             Client2Record client2Record = mSessionToClientMap.get(sessionInfo.getId());
             if (client2Record == null) {
@@ -1208,8 +1209,8 @@
             // 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) {
@@ -1228,7 +1229,7 @@
         }
 
         private void notifySessionInfoChanged(Client2Record clientRecord,
-                RouteSessionInfo sessionInfo) {
+                RoutingSessionInfo sessionInfo) {
             try {
                 clientRecord.mClient.notifySessionInfoChanged(sessionInfo);
             } catch (RemoteException ex) {
@@ -1238,7 +1239,7 @@
         }
 
         private void notifySessionReleased(Client2Record clientRecord,
-                RouteSessionInfo sessionInfo) {
+                RoutingSessionInfo sessionInfo) {
             try {
                 clientRecord.mClient.notifySessionReleased(sessionInfo);
             } catch (RemoteException ex) {
@@ -1412,8 +1413,8 @@
                 try {
                     manager.notifyRouteSelected(clientRecord.mPackageName,
                             clientRecord.mSelectedRoute);
-                    manager.notifyRouteTypesChanged(clientRecord.mPackageName,
-                            clientRecord.mDiscoveryRequest.getRouteTypes());
+                    manager.notifyPreferredFeaturesChanged(clientRecord.mPackageName,
+                            clientRecord.mDiscoveryPreference.getPreferredFeatures());
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "Failed to update client usage. Manager probably died.", ex);
                 }
@@ -1432,15 +1433,15 @@
         final class SessionCreationRequest {
             public final Client2Record mClientRecord;
             public final MediaRoute2Info mRoute;
-            public final String mRouteType;
+            public final String mRouteFeature;
             public final long mRequestId;
 
             SessionCreationRequest(@NonNull Client2Record clientRecord,
                     @NonNull MediaRoute2Info route,
-                    @NonNull String routeType, long requestId) {
+                    @NonNull String routeFeature, long requestId) {
                 mClientRecord = clientRecord;
                 mRoute = route;
-                mRouteType = routeType;
+                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 c76555c..b7aa484 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -39,8 +39,8 @@
 import android.media.MediaRouterClientState;
 import android.media.RemoteDisplayState;
 import android.media.RemoteDisplayState.RemoteDisplayInfo;
-import android.media.RouteDiscoveryRequest;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -520,7 +520,7 @@
     }
     // Binder call
     @Override
-    public void setDiscoveryRequest2(IMediaRouter2Client client, RouteDiscoveryRequest request) {
+    public void setDiscoveryRequest2(IMediaRouter2Client client, RouteDiscoveryPreference request) {
         mService2.setDiscoveryRequest2(client, request);
     }
 
@@ -552,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/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 0ea4e63..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.
@@ -55,6 +55,7 @@
     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,6 +88,10 @@
         mAudioService = IAudioService.Stub.asInterface(
                 ServiceManager.getService(Context.AUDIO_SERVICE));
 
+        mBtRouteProvider = BluetoothRouteProvider.getInstance(context, (routes) -> {
+            mBluetoothRoutes = routes;
+            publishRoutes();
+        });
         initializeRoutes();
     }
 
@@ -141,8 +146,8 @@
                         : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
                 .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
                 .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
-                .addRouteType(TYPE_LIVE_AUDIO)
-                .addRouteType(TYPE_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))
-                .addRouteType(TYPE_LIVE_AUDIO)
-                .addRouteType(TYPE_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())
-                        .addRouteType(TYPE_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/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 7a6f297..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;
@@ -71,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;
@@ -97,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;
@@ -109,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;
@@ -176,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";
 
@@ -220,6 +226,7 @@
      */
     public interface NetworkStatsSettings {
         public long getPollInterval();
+        public long getPollDelay();
         public boolean getSampleEnabled();
         public boolean getAugmentEnabled();
 
@@ -248,6 +255,7 @@
     }
 
     private final Object mStatsLock = new Object();
+    private final Object mStatsProviderLock = new Object();
 
     /** Set of currently active ifaces. */
     @GuardedBy("mStatsLock")
@@ -272,6 +280,9 @@
     private final DropBoxNonMonotonicObserver mNonMonotonicObserver =
             new DropBoxNonMonotonicObserver();
 
+    private final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList =
+            new RemoteCallbackList<>();
+
     @GuardedBy("mStatsLock")
     private NetworkStatsRecorder mDevRecorder;
     @GuardedBy("mStatsLock")
@@ -502,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 {
@@ -514,6 +525,7 @@
         } catch (RemoteException e) {
             // ignored; service lives in system_server
         }
+        invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.setAlert(mGlobalAlertBytes));
     }
 
     @Override
@@ -803,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) {
@@ -1095,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
@@ -1106,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());
                 }
             }
         }
@@ -1252,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");
@@ -1355,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);
@@ -1476,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
@@ -1690,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;
@@ -1726,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;
@@ -1815,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/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/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index a009183..2807909 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -55,7 +55,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -376,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) {
@@ -383,7 +409,6 @@
                 }
                 try {
                     mAllPackagesCache = new ArrayList<>();
-                    mPackageNameToApexModuleName = new HashMap<>();
                     HashSet<String> activePackagesSet = new HashSet<>();
                     HashSet<String> factoryPackagesSet = new HashSet<>();
                     final ApexInfo[] allPkgs = mApexService.getAllPackages();
@@ -409,7 +434,6 @@
                         final PackageInfo packageInfo =
                                 PackageParser.generatePackageInfo(pkg, ai, flags);
                         mAllPackagesCache.add(packageInfo);
-                        mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName);
                         if (ai.isActive) {
                             if (activePackagesSet.contains(packageInfo.packageName)) {
                                 throw new IllegalStateException(
@@ -612,8 +636,7 @@
 
         @Override
         List<String> getApksInApex(String apexPackageName) {
-            // TODO(b/142712057): Avoid calling populateAllPackagesCacheIfNeeded during boot.
-            populateAllPackagesCacheIfNeeded();
+            populatePackageNameToApexModuleNameIfNeeded();
             synchronized (mLock) {
                 String moduleName = mPackageNameToApexModuleName.get(apexPackageName);
                 if (moduleName == null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 17870eb..dad32cd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -11602,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);
@@ -15210,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) {
@@ -23249,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;
                 }
@@ -23270,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;
             }
         }
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 ec84b51..3e665c3 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4742,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/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/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index 8385f40..a641f06 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -24,6 +24,7 @@
 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;
@@ -74,6 +75,8 @@
             @NonNull Properties hidlProperties) {
         SoundTriggerModuleProperties aidlProperties = hidl2aidlProperties(hidlProperties.base);
         aidlProperties.supportedModelArch = hidlProperties.supportedModelArch;
+        aidlProperties.audioCapabilities =
+                hidl2aidlAudioCapabilities(hidlProperties.audioCapabilities);
         return aidlProperties;
     }
 
@@ -209,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;
     }
 
@@ -395,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 dbf91a9..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,17 @@
         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;
     }
 
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 2f024a5..8b434bd 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
@@ -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 3354c56..2f087f4 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -217,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();
@@ -369,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/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/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index f661b5e..c964795 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -21,7 +21,6 @@
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
 import android.os.TimestampedValue;
 
 import java.io.PrintWriter;
@@ -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 da848d8..e95fc4a 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -23,9 +23,7 @@
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
 import android.os.TimestampedValue;
-import android.telephony.TelephonyManager;
 import android.util.LocalLog;
 import android.util.Slog;
 
@@ -535,17 +533,6 @@
         } else {
             mLastAutoSystemClockTimeSet = null;
         }
-
-        // Historically, Android has sent a TelephonyManager.ACTION_NETWORK_SET_TIME broadcast only
-        // when setting the time using NITZ.
-        if (origin == ORIGIN_PHONE) {
-            // Send a broadcast that telephony code used to send after setting the clock.
-            // TODO Remove this broadcast as soon as there are no remaining listeners.
-            Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_SET_TIME);
-            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-            intent.putExtra("time", newSystemClockMillis);
-            mCallback.sendStickyBroadcast(intent);
-        }
     }
 
     /**
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d5ff280..6b81fdd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -129,6 +129,7 @@
 import android.app.admin.DevicePolicyManager.PasswordComplexity;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.DeviceStateCache;
+import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.NetworkEvent;
 import android.app.admin.PasswordMetrics;
 import android.app.admin.PasswordPolicy;
@@ -268,6 +269,7 @@
 import com.android.internal.widget.PasswordValidationError;
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
+import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
 import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
@@ -1006,6 +1008,8 @@
         private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL =
                 "cross-profile-calendar-packages-null";
         private static final String TAG_CROSS_PROFILE_PACKAGES = "cross-profile-packages";
+        private static final String TAG_FACTORY_RESET_PROTECTION_POLICY =
+                "factory_reset_protection_policy";
 
         DeviceAdminInfo info;
 
@@ -1016,6 +1020,9 @@
         @NonNull
         PasswordPolicy mPasswordPolicy = new PasswordPolicy();
 
+        @Nullable
+        FactoryResetProtectionPolicy mFactoryResetProtectionPolicy = null;
+
         static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0;
         long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK;
 
@@ -1351,6 +1358,11 @@
                         mCrossProfileCalendarPackages);
             }
             writePackageListToXml(out, TAG_CROSS_PROFILE_PACKAGES, mCrossProfilePackages);
+            if (mFactoryResetProtectionPolicy != null) {
+                out.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+                mFactoryResetProtectionPolicy.writeToXml(out);
+                out.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+            }
         }
 
         void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException {
@@ -1584,6 +1596,9 @@
                     mCrossProfileCalendarPackages = null;
                 } else if (TAG_CROSS_PROFILE_PACKAGES.equals(tag)) {
                     mCrossProfilePackages = readPackageList(parser, tag);
+                } else if (TAG_FACTORY_RESET_PROTECTION_POLICY.equals(tag)) {
+                    mFactoryResetProtectionPolicy = FactoryResetProtectionPolicy.readFromXml(
+                                parser);
                 } else {
                     Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
                     XmlUtils.skipCurrentTag(parser);
@@ -2036,6 +2051,10 @@
             return IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE));
         }
 
+        PersistentDataBlockManagerInternal getPersistentDataBlockManagerInternal() {
+            return LocalServices.getService(PersistentDataBlockManagerInternal.class);
+        }
+
         LockSettingsInternal getLockSettingsInternal() {
             return LocalServices.getService(LockSettingsInternal.class);
         }
@@ -6736,6 +6755,67 @@
     }
 
     @Override
+    public void setFactoryResetProtectionPolicy(ComponentName who,
+            @Nullable FactoryResetProtectionPolicy policy) {
+        if (!mHasFeature) {
+            return;
+        }
+        Preconditions.checkNotNull(who, "ComponentName is null");
+
+        final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
+        final int userId = mInjector.userHandleGetCallingUserId();
+        synchronized (getLockObject()) {
+            ActiveAdmin admin = getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+            admin.mFactoryResetProtectionPolicy = policy;
+            saveSettingsLocked(userId);
+        }
+
+        mInjector.binderWithCleanCallingIdentity(() -> mContext.sendBroadcastAsUser(
+                new Intent(DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+                UserHandle.getUserHandleForUid(frpManagementAgentUid)));
+
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.SET_FACTORY_RESET_PROTECTION)
+                .setAdmin(who)
+                .write();
+    }
+
+    @Override
+    public FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(
+            @Nullable ComponentName who) {
+        if (!mHasFeature) {
+            return null;
+        }
+
+        final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
+        ActiveAdmin admin;
+        synchronized (getLockObject()) {
+            if (who == null) {
+                if ((frpManagementAgentUid != mInjector.binderGetCallingUid())) {
+                    throw new SecurityException(
+                            "Must be called by the FRP management agent on device");
+                }
+                admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+                        UserHandle.getUserId(frpManagementAgentUid));
+            } else {
+                admin = getActiveAdminForCallerLocked(
+                        who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+            }
+        }
+        return admin != null ? admin.mFactoryResetProtectionPolicy : null;
+    }
+
+    private int getFrpManagementAgentUidOrThrow() {
+        PersistentDataBlockManagerInternal pdb = mInjector.getPersistentDataBlockManagerInternal();
+        if ((pdb == null) || (pdb.getAllowedUid() == -1)) {
+            throw new UnsupportedOperationException(
+                    "The persistent data block service is not supported on this device");
+        }
+        return pdb.getAllowedUid();
+    }
+
+    @Override
     public void getRemoveWarning(ComponentName comp, final RemoteCallback result, int userHandle) {
         if (!mHasFeature) {
             return;
@@ -8131,6 +8211,14 @@
         return null;
     }
 
+    ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(int userId) {
+        ActiveAdmin admin = getDeviceOwnerAdminLocked();
+        if (admin == null) {
+            admin = getProfileOwnerOfOrganizationOwnedDeviceLocked(userId);
+        }
+        return admin;
+    }
+
     @Override
     public void clearDeviceOwner(String packageName) {
         Objects.requireNonNull(packageName, "packageName is null");
@@ -12212,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);
diff --git a/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java b/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java
index a8a258f..9c5d4ad 100644
--- a/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java
+++ b/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java
@@ -3,6 +3,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 
 import android.os.Looper;
 import android.os.SystemClock;
@@ -52,8 +53,10 @@
 
     @Test
     public void handleInjectNtpTime_cachedAgeLow_injectTime() throws InterruptedException {
-        doReturn(NtpTimeHelper.NTP_INTERVAL - 1).when(mMockNtpTrustedTime).getCacheAge();
-        doReturn(MOCK_NTP_TIME).when(mMockNtpTrustedTime).getCachedNtpTime();
+        NtpTrustedTime.TimeResult result = mock(NtpTrustedTime.TimeResult.class);
+        doReturn(NtpTimeHelper.NTP_INTERVAL - 1).when(result).getAgeMillis();
+        doReturn(MOCK_NTP_TIME).when(result).getTimeMillis();
+        doReturn(result).when(mMockNtpTrustedTime).getCachedTimeResult();
 
         mNtpTimeHelper.retrieveAndInjectNtpTime();
 
@@ -64,7 +67,9 @@
     @Test
     public void handleInjectNtpTime_injectTimeFailed_injectTimeDelayed()
             throws InterruptedException {
-        doReturn(NtpTimeHelper.NTP_INTERVAL + 1).when(mMockNtpTrustedTime).getCacheAge();
+        NtpTrustedTime.TimeResult result1 = mock(NtpTrustedTime.TimeResult.class);
+        doReturn(NtpTimeHelper.NTP_INTERVAL + 1).when(result1).getAgeMillis();
+        doReturn(result1).when(mMockNtpTrustedTime).getCachedTimeResult();
         doReturn(false).when(mMockNtpTrustedTime).forceRefresh();
 
         mNtpTimeHelper.retrieveAndInjectNtpTime();
@@ -72,8 +77,10 @@
         assertThat(mCountDownLatch.await(2, TimeUnit.SECONDS)).isFalse();
 
         doReturn(true).when(mMockNtpTrustedTime).forceRefresh();
-        doReturn(1L).when(mMockNtpTrustedTime).getCacheAge();
-        doReturn(MOCK_NTP_TIME).when(mMockNtpTrustedTime).getCachedNtpTime();
+        NtpTrustedTime.TimeResult result2 = mock(NtpTrustedTime.TimeResult.class);
+        doReturn(1L).when(result2).getAgeMillis();
+        doReturn(MOCK_NTP_TIME).when(result2).getTimeMillis();
+        doReturn(result2).when(mMockNtpTrustedTime).getCachedTimeResult();
         SystemClock.sleep(NtpTimeHelper.RETRY_INTERVAL);
 
         waitForTasksToBePostedOnHandlerAndRunThem();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 591c3a3..c223f13 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -804,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/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
index d44476e..3de006c 100644
--- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -57,7 +57,6 @@
 
 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;
@@ -523,7 +522,6 @@
      * Test that {@link BackupManagerService#dump()} dumps system user information before non-system
      * user information.
      */
-
     @Test
     public void testDump_systemUserFirst() {
         String[] args = new String[0];
@@ -539,16 +537,31 @@
     }
 
     @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/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index ac555fd..3a8258b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -51,6 +51,7 @@
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 
 import java.io.File;
@@ -223,6 +224,11 @@
         }
 
         @Override
+        PersistentDataBlockManagerInternal getPersistentDataBlockManagerInternal() {
+            return services.persistentDataBlockManagerInternal;
+        }
+
+        @Override
         Looper getMyLooper() {
             return Looper.getMainLooper();
         }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 175c756..21034d3 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -31,6 +31,8 @@
 import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
 import static com.android.server.testutils.TestUtils.assertExpectException;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
@@ -62,6 +64,7 @@
 import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.PasswordMetrics;
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
@@ -2022,6 +2025,116 @@
         );
     }
 
+    public void testSetFactoryResetProtectionPolicyWithDO() throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+
+        when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+                DpmMockContext.CALLER_UID);
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionAccounts(new ArrayList<>())
+                .setFactoryResetProtectionDisabled(true)
+                .build();
+        dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+        FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(admin1);
+        assertThat(result).isEqualTo(policy);
+        assertPoliciesAreEqual(policy, result);
+
+        verify(mContext.spiedContext).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+    }
+
+    public void testSetFactoryResetProtectionPolicyFailWithPO() throws Exception {
+        setupProfileOwner();
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionDisabled(true)
+                .build();
+
+        assertExpectException(SecurityException.class, null,
+                () -> dpm.setFactoryResetProtectionPolicy(admin1, policy));
+    }
+
+    public void testSetFactoryResetProtectionPolicyWithPOOfOrganizationOwnedDevice()
+            throws Exception {
+        setupProfileOwner();
+        configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+        when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+                DpmMockContext.CALLER_UID);
+
+        List<String> accounts = new ArrayList<>();
+        accounts.add("Account 1");
+        accounts.add("Account 2");
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionAccounts(accounts)
+                .build();
+
+        dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+        FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(admin1);
+        assertThat(result).isEqualTo(policy);
+        assertPoliciesAreEqual(policy, result);
+
+        verify(mContext.spiedContext, times(2)).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+        verify(mContext.spiedContext).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+        verify(mContext.spiedContext).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+    }
+
+    public void testGetFactoryResetProtectionPolicyWithFrpManagementAgent()
+            throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+        when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+                DpmMockContext.CALLER_UID);
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionAccounts(new ArrayList<>())
+                .setFactoryResetProtectionDisabled(true)
+                .build();
+
+        dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+        mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        dpm.setActiveAdmin(admin1, /*replace=*/ false);
+        FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(null);
+        assertThat(result).isEqualTo(policy);
+        assertPoliciesAreEqual(policy, result);
+
+        verify(mContext.spiedContext).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(
+                        DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+                MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+    }
+
+    private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy,
+            FactoryResetProtectionPolicy actualPolicy) {
+        assertThat(actualPolicy.isFactoryResetProtectionDisabled()).isEqualTo(
+                expectedPolicy.isFactoryResetProtectionDisabled());
+        assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(),
+                actualPolicy.getFactoryResetProtectionAccounts());
+    }
+
+    private void assertAccountsAreEqual(List<String> expectedAccounts,
+            List<String> actualAccounts) {
+        assertThat(actualAccounts).containsExactlyElementsIn(expectedAccounts);
+    }
+
     public void testGetMacAddress() throws Exception {
         mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
@@ -2845,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);
@@ -2856,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(
@@ -2882,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);
@@ -2890,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);
@@ -2901,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(
@@ -2913,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(
@@ -2938,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 */);
@@ -2951,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(
@@ -2995,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 */);
@@ -3008,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(
@@ -3024,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,
@@ -3153,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,
@@ -3167,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(
@@ -3193,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,
@@ -3207,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(
@@ -3232,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);
@@ -3244,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(
@@ -3271,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 */);
@@ -3284,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(
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
new file mode 100644
index 0000000..bc853c6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.devicepolicy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.FactoryResetProtectionPolicy;
+import android.os.Parcel;
+import android.util.Xml;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link android.app.admin.FactoryResetProtectionPolicy}.
+ *
+ * atest com.android.server.devicepolicy.FactoryResetProtectionPolicyTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class FactoryResetProtectionPolicyTest {
+
+    private static final String TAG_FACTORY_RESET_PROTECTION_POLICY =
+            "factory_reset_protection_policy";
+
+    @Test
+    public void testNonDefaultFactoryResetProtectionPolicyObject() throws Exception {
+        List<String> accounts = new ArrayList<>();
+        accounts.add("Account 1");
+        accounts.add("Account 2");
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionAccounts(accounts)
+                .setFactoryResetProtectionDisabled(true)
+                .build();
+
+        testParcelAndUnparcel(policy);
+        testSerializationAndDeserialization(policy);
+    }
+
+    @Test
+    public void testInvalidXmlFactoryResetProtectionPolicyObject() throws Exception {
+        List<String> accounts = new ArrayList<>();
+        accounts.add("Account 1");
+        accounts.add("Account 2");
+
+        FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+                .setFactoryResetProtectionAccounts(accounts)
+                .setFactoryResetProtectionDisabled(true)
+                .build();
+
+        testParcelAndUnparcel(policy);
+        testInvalidXmlSerializationAndDeserialization(policy);
+    }
+
+    private void testParcelAndUnparcel(FactoryResetProtectionPolicy policy) {
+        Parcel parcel = Parcel.obtain();
+        policy.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        FactoryResetProtectionPolicy actualPolicy =
+                FactoryResetProtectionPolicy.CREATOR.createFromParcel(parcel);
+        assertPoliciesAreEqual(policy, actualPolicy);
+        parcel.recycle();
+    }
+
+    private void testSerializationAndDeserialization(FactoryResetProtectionPolicy policy)
+            throws Exception {
+        ByteArrayOutputStream outStream = serialize(policy);
+        ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new InputStreamReader(inStream));
+        assertEquals(XmlPullParser.START_TAG, parser.next());
+
+        assertPoliciesAreEqual(policy, policy.readFromXml(parser));
+    }
+
+    private void testInvalidXmlSerializationAndDeserialization(FactoryResetProtectionPolicy policy)
+            throws Exception {
+        ByteArrayOutputStream outStream = serialize(policy);
+        ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
+        XmlPullParser parser = mock(XmlPullParser.class);
+        when(parser.next()).thenThrow(XmlPullParserException.class);
+        parser.setInput(new InputStreamReader(inStream));
+
+        // If deserialization fails, then null is returned.
+        assertNull(policy.readFromXml(parser));
+    }
+
+    private ByteArrayOutputStream serialize(FactoryResetProtectionPolicy policy)
+            throws IOException {
+        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+        final XmlSerializer outXml = new FastXmlSerializer();
+        outXml.setOutput(outStream, StandardCharsets.UTF_8.name());
+        outXml.startDocument(null, true);
+        outXml.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+        policy.writeToXml(outXml);
+        outXml.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+        outXml.endDocument();
+        outXml.flush();
+        return outStream;
+    }
+
+    private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy,
+            FactoryResetProtectionPolicy actualPolicy) {
+        assertEquals(expectedPolicy.isFactoryResetProtectionDisabled(),
+                actualPolicy.isFactoryResetProtectionDisabled());
+        assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(),
+                actualPolicy.getFactoryResetProtectionAccounts());
+    }
+
+    private void assertAccountsAreEqual(List<String> expectedAccounts,
+            List<String> actualAccounts) {
+        assertEquals(expectedAccounts.size(), actualAccounts.size());
+        for (int i = 0; i < expectedAccounts.size(); i++) {
+            assertEquals(expectedAccounts.get(i), actualAccounts.get(i));
+        }
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 919a3f6..6c2c144 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -68,6 +68,7 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.PersistentDataBlockManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
@@ -117,6 +118,7 @@
     public final TimeDetector timeDetector;
     public final TimeZoneDetector timeZoneDetector;
     public final KeyChain.KeyChainConnection keyChainConnection;
+    public final PersistentDataBlockManagerInternal persistentDataBlockManagerInternal;
     /** Note this is a partial mock, not a real mock. */
     public final PackageManager packageManager;
     public final BuildMock buildMock = new BuildMock();
@@ -160,6 +162,7 @@
         timeDetector = mock(TimeDetector.class);
         timeZoneDetector = mock(TimeZoneDetector.class);
         keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS);
+        persistentDataBlockManagerInternal = mock(PersistentDataBlockManagerInternal.class);
 
         // Package manager is huge, so we use a partial mock instead.
         packageManager = spy(realContext.getPackageManager());
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/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
index 51f5c75..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
@@ -48,7 +48,6 @@
 import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -656,65 +655,4 @@
                 /* expectedExceptionMessageRegex */ "A rule must end with a '1' bit",
                 () -> binaryParser.parse(rule.array()));
     }
-
-    @Test
-    public void testBinaryStream_multipleRules_indexingIdentifiesParsesIndexRangeCorrectly()
-            throws Exception {
-        String packageName2 = "com.test.2";
-
-        byte[] ruleBytes1 = getBytes(getRulesWithPackageName("com.test.1"));
-        byte[] ruleBytes2 = getBytes(getRulesWithPackageName(packageName2));
-        byte[] ruleBytes3 = getBytes(getRulesWithPackageName("com.test.3"));
-
-        ByteBuffer rule =
-                ByteBuffer.allocate(
-                        DEFAULT_FORMAT_VERSION_BYTES.length
-                                + ruleBytes1.length
-                                + ruleBytes2.length
-                                + ruleBytes3.length);
-        rule.put(DEFAULT_FORMAT_VERSION_BYTES);
-        rule.put(ruleBytes1);
-        rule.put(ruleBytes2);
-        rule.put(ruleBytes3);
-        InputStream inputStream = new ByteArrayInputStream(rule.array());
-
-        RuleParser binaryParser = new RuleBinaryParser();
-
-        List<RuleIndexRange> indexRanges = new ArrayList<>();
-        indexRanges.add(
-                new RuleIndexRange(
-                        DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length,
-                        DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes1.length
-                                + ruleBytes2.length));
-        List<Rule> rules = binaryParser.parse(inputStream, indexRanges);
-
-        Rule expectedRule =
-                new Rule(
-                        new CompoundFormula(
-                                CompoundFormula.NOT,
-                                Collections.singletonList(
-                                        new AtomicFormula.StringAtomicFormula(
-                                                AtomicFormula.PACKAGE_NAME,
-                                                packageName2,
-                                                /* isHashedValue= */ false))),
-                        Rule.DENY);
-
-        assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
-    }
-
-    private static String getRulesWithPackageName(String packageName) {
-        return 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;
-
-    }
 }
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 c7dbad8..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;
@@ -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/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index 0f75816..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;
@@ -161,6 +163,9 @@
                 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;
     }
 
@@ -185,8 +190,11 @@
 
         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);
         }
     }
 
@@ -330,12 +338,17 @@
         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);
@@ -354,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));
@@ -371,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);
@@ -400,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));
@@ -418,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 {
@@ -429,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(
@@ -456,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));
@@ -488,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;
 
@@ -500,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(
@@ -521,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));
@@ -554,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 {
@@ -572,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();
 
@@ -606,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;
@@ -618,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();
 
@@ -648,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);
         }
     }
 
@@ -672,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;
     }
 
@@ -687,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());
         }
     }
 
@@ -798,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();
     }
@@ -810,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();
     }
@@ -823,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);
@@ -844,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);
@@ -865,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,
@@ -894,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,
@@ -930,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);
@@ -973,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);
@@ -1013,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);
@@ -1061,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);
@@ -1109,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);
@@ -1145,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);
@@ -1182,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
@@ -1218,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);
@@ -1239,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
@@ -1272,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
@@ -1304,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/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index aaf9799..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,7 +25,6 @@
 import android.app.timedetector.ManualTimeSuggestion;
 import android.app.timedetector.NetworkTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
 import android.icu.util.Calendar;
 import android.icu.util.GregorianCalendar;
 import android.icu.util.TimeZone;
@@ -78,8 +76,7 @@
 
         long expectedSystemClockMillis =
                 mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
-        mScript.verifySystemClockWasSetAndResetCallTracking(
-                expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+        mScript.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
                 .assertLatestPhoneSuggestion(phoneId, timeSuggestion);
     }
 
@@ -118,8 +115,7 @@
                     mScript.calculateTimeInMillisForNow(timeSuggestion1.getUtcTime());
 
             mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
-                    .verifySystemClockWasSetAndResetCallTracking(
-                            expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+                    .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
                     .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
         }
 
@@ -146,8 +142,7 @@
                     mScript.calculateTimeInMillisForNow(timeSuggestion3.getUtcTime());
 
             mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
-                    .verifySystemClockWasSetAndResetCallTracking(
-                            expectedSystemClockMillis3, true /* expectNetworkBroadcast */)
+                    .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis3)
                     .assertLatestPhoneSuggestion(phoneId, timeSuggestion3);
         }
     }
@@ -175,8 +170,7 @@
                     mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime());
 
             mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
-                    .verifySystemClockWasSetAndResetCallTracking(
-                            expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+                    .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
                     .assertLatestPhoneSuggestion(phone1Id, null)
                     .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
         }
@@ -193,8 +187,7 @@
                     mScript.calculateTimeInMillisForNow(phone1TimeSuggestion.getUtcTime());
 
             mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion)
-                    .verifySystemClockWasSetAndResetCallTracking(
-                            expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+                    .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
                     .assertLatestPhoneSuggestion(phone1Id, phone1TimeSuggestion);
 
         }
@@ -227,8 +220,7 @@
                     mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime());
 
             mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
-                    .verifySystemClockWasSetAndResetCallTracking(
-                            expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+                    .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
                     .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
         }
     }
@@ -265,8 +257,7 @@
         mScript.simulateTimePassing();
         long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(utcTime1);
         mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
                 .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
 
         // The UTC time increment should be larger than the system clock update threshold so we
@@ -304,8 +295,7 @@
         PhoneTimeSuggestion timeSuggestion4 =
                 createPhoneTimeSuggestion(phoneId, utcTime4);
         mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis4, true /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4)
                 .assertLatestPhoneSuggestion(phoneId, timeSuggestion4);
     }
 
@@ -339,8 +329,7 @@
 
         // Turn on auto time detection.
         mScript.simulateAutoTimeDetectionToggle()
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
                 .assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
 
         // Turn off auto time detection.
@@ -367,8 +356,7 @@
 
         // Turn on auto time detection.
         mScript.simulateAutoTimeDetectionToggle()
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis2, true /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2)
                 .assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
     }
 
@@ -388,7 +376,7 @@
                 mScript.calculateTimeInMillisForNow(phoneSuggestion.getUtcTime());
         mScript.simulatePhoneTimeSuggestion(phoneSuggestion)
                 .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis, true /* expectedNetworkBroadcast */)
+                        expectedSystemClockMillis  /* expectedNetworkBroadcast */)
                 .assertLatestPhoneSuggestion(phoneId, phoneSuggestion);
 
         // Look inside and check what the strategy considers the current best phone suggestion.
@@ -416,8 +404,7 @@
         long expectedSystemClockMillis =
                 mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
         mScript.simulateManualTimeSuggestion(timeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis, false /* expectNetworkBroadcast */);
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
     }
 
     @Test
@@ -439,8 +426,7 @@
         long expectedAutoClockMillis =
                 mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime());
         mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedAutoClockMillis, true /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
                 .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
 
         // Simulate the passage of time.
@@ -463,8 +449,7 @@
         long expectedManualClockMillis =
                 mScript.calculateTimeInMillisForNow(manualTimeSuggestion.getUtcTime());
         mScript.simulateManualTimeSuggestion(manualTimeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedManualClockMillis, false /* expectNetworkBroadcast */)
+                .verifySystemClockWasSetAndResetCallTracking(expectedManualClockMillis)
                 .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
 
         // Simulate the passage of time.
@@ -475,8 +460,7 @@
 
         expectedAutoClockMillis =
                 mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime());
-        mScript.verifySystemClockWasSetAndResetCallTracking(
-                        expectedAutoClockMillis, true /* expectNetworkBroadcast */)
+        mScript.verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
                 .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
 
         // Switch back to manual - nothing should happen to the clock.
@@ -514,8 +498,7 @@
         long expectedSystemClockMillis =
                 mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
         mScript.simulateNetworkTimeSuggestion(timeSuggestion)
-                .verifySystemClockWasSetAndResetCallTracking(
-                        expectedSystemClockMillis, false /* expectNetworkBroadcast */);
+                .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
     }
 
     @Test
@@ -550,8 +533,7 @@
         mScript.simulateTimePassing(smallTimeIncrementMillis)
                 .simulateNetworkTimeSuggestion(networkTimeSuggestion1)
                 .verifySystemClockWasSetAndResetCallTracking(
-                        mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime()),
-                        false /* expectNetworkBroadcast */);
+                        mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime()));
 
         // Check internal state.
         mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, null)
@@ -570,8 +552,7 @@
         mScript.simulateTimePassing(smallTimeIncrementMillis)
                 .simulatePhoneTimeSuggestion(phoneTimeSuggestion)
                 .verifySystemClockWasSetAndResetCallTracking(
-                        mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()),
-                        true /* expectNetworkBroadcast */);
+                        mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()));
 
         // Check internal state.
         mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
@@ -622,8 +603,7 @@
 
         // Verify the latest network time now wins.
         mScript.verifySystemClockWasSetAndResetCallTracking(
-                mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime()),
-                false /* expectNetworkTimeBroadcast */);
+                mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime()));
 
         // Check internal state.
         mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
@@ -645,7 +625,6 @@
 
         // Tracking operations.
         private boolean mSystemClockWasSet;
-        private Intent mBroadcastSent;
 
         @Override
         public int systemClockUpdateThresholdMillis() {
@@ -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/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 06c8074..6b95f0f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -159,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
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6a62237..8668536 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -848,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";
 
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index eb328a7..4f104f4 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2448,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);
+        }
     }
 
     /**
@@ -2479,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) {
@@ -2488,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
@@ -2531,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) {
@@ -2542,7 +2550,7 @@
         } catch (RemoteException ex) {
             // ignore it
         }
-        return null;
+        return new Bundle();
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f321427..157d92d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1444,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>
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/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/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 6de068e..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;
 
@@ -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/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/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/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index 2b7f8af..a77d30a 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -32,6 +32,9 @@
 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;
 
@@ -182,6 +185,22 @@
     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.
      */
@@ -219,7 +238,9 @@
     /** Private constructor for Builder and Parcelable implementation. */
     private SoftApConfiguration(@Nullable String ssid, @Nullable MacAddress bssid,
             @Nullable String passphrase, boolean hiddenSsid, @BandType int band, int channel,
-            @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis) {
+            @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis,
+            boolean clientControlByUser, @NonNull List<MacAddress> blockedList,
+            @NonNull List<MacAddress> allowedList) {
         mSsid = ssid;
         mBssid = bssid;
         mPassphrase = passphrase;
@@ -229,6 +250,9 @@
         mSecurityType = securityType;
         mMaxNumberOfClients = maxNumberOfClients;
         mShutdownTimeoutMillis = shutdownTimeoutMillis;
+        mClientControlByUser = clientControlByUser;
+        mBlockedClientList = new ArrayList<>(blockedList);
+        mAllowedClientList = new ArrayList<>(allowedList);
     }
 
     @Override
@@ -248,13 +272,17 @@
                 && mChannel == other.mChannel
                 && mSecurityType == other.mSecurityType
                 && mMaxNumberOfClients == other.mMaxNumberOfClients
-                && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis;
+                && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis
+                && mClientControlByUser == other.mClientControlByUser
+                && Objects.equals(mBlockedClientList, other.mBlockedClientList)
+                && Objects.equals(mAllowedClientList, other.mAllowedClientList);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid,
-                mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis);
+                mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis,
+                mClientControlByUser, mBlockedClientList, mAllowedClientList);
     }
 
     @Override
@@ -270,6 +298,9 @@
         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();
     }
 
@@ -284,6 +315,9 @@
         dest.writeInt(mSecurityType);
         dest.writeInt(mMaxNumberOfClients);
         dest.writeInt(mShutdownTimeoutMillis);
+        dest.writeBoolean(mClientControlByUser);
+        dest.writeTypedList(mBlockedClientList);
+        dest.writeTypedList(mAllowedClientList);
     }
 
     @Override
@@ -299,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.readInt(), in.readBoolean(),
+                    in.createTypedArrayList(MacAddress.CREATOR),
+                    in.createTypedArrayList(MacAddress.CREATOR));
         }
 
         @Override
@@ -387,6 +423,34 @@
     }
 
     /**
+     * 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.
      *
@@ -403,6 +467,9 @@
         private int mMaxNumberOfClients;
         private int mSecurityType;
         private int mShutdownTimeoutMillis;
+        private boolean mClientControlByUser;
+        private List<MacAddress> mBlockedClientList;
+        private List<MacAddress> mAllowedClientList;
 
         /**
          * Constructs a Builder with default values (see {@link Builder}).
@@ -417,6 +484,9 @@
             mMaxNumberOfClients = 0;
             mSecurityType = SECURITY_TYPE_OPEN;
             mShutdownTimeoutMillis = 0;
+            mClientControlByUser = false;
+            mBlockedClientList = new ArrayList<>();
+            mAllowedClientList = new ArrayList<>();
         }
 
         /**
@@ -434,6 +504,9 @@
             mMaxNumberOfClients = other.mMaxNumberOfClients;
             mSecurityType = other.mSecurityType;
             mShutdownTimeoutMillis = other.mShutdownTimeoutMillis;
+            mClientControlByUser = other.mClientControlByUser;
+            mBlockedClientList = new ArrayList<>(other.mBlockedClientList);
+            mAllowedClientList = new ArrayList<>(other.mAllowedClientList);
         }
 
         /**
@@ -445,7 +518,8 @@
         public SoftApConfiguration build() {
             return new SoftApConfiguration(mSsid, mBssid, mPassphrase,
                     mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients,
-                    mShutdownTimeoutMillis);
+                    mShutdownTimeoutMillis, mClientControlByUser, mBlockedClientList,
+                    mAllowedClientList);
         }
 
         /**
@@ -662,5 +736,82 @@
             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/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 1baab12..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
@@ -666,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
      */
@@ -691,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 = {
@@ -3229,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
@@ -3460,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) {}
 
@@ -3489,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.
+        }
     }
 
     /**
@@ -3555,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);
+            });
+        }
     }
 
     /**
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/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index eeea7e2..6884a4e 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -25,6 +25,8 @@
 
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Random;
 
 @SmallTest
@@ -112,12 +114,18 @@
 
     @Test
     public void testWpa2WithAllFieldCustomized() {
+        List<MacAddress> testBlockedClientList = new ArrayList<>();
+        List<MacAddress> testAllowedClientList = new ArrayList<>();
+        testBlockedClientList.add(MacAddress.fromString("11:22:33:44:55:66"));
+        testAllowedClientList.add(MacAddress.fromString("aa:bb:cc:dd:ee:ff"));
         SoftApConfiguration original = new SoftApConfiguration.Builder()
                 .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                 .setChannel(149, SoftApConfiguration.BAND_5GHZ)
                 .setHiddenSsid(true)
                 .setMaxNumberOfClients(10)
                 .setShutdownTimeoutMillis(500000)
+                .enableClientControlByUser(true)
+                .setClientList(testBlockedClientList, testAllowedClientList)
                 .build();
         assertThat(original.getPassphrase()).isEqualTo("secretsecret");
         assertThat(original.getSecurityType()).isEqualTo(
@@ -127,6 +135,9 @@
         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);
@@ -238,4 +249,17 @@
                 .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/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 4b83718..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
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();