Merge "Parse apk container of apex files using lite parser during boot"
diff --git a/Android.bp b/Android.bp
index 0d9cbd8..abd7d2d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -416,6 +416,13 @@
 }
 
 filegroup {
+    name: "graphicsstats_proto",
+    srcs: [
+        "libs/hwui/protos/graphicsstats.proto",
+    ],
+}
+
+filegroup {
     name: "libvibrator_aidl",
     srcs: [
         "core/java/android/os/IExternalVibrationController.aidl",
@@ -480,14 +487,15 @@
         "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",
         // TODO(b/140299412): should be framework-wifi-stubs
         "framework-wifi",
         "ike-stubs",
-        // TODO(jiyong): add more stubs for APEXes here
+        // TODO(b/147200698): should be the stub of framework-tethering
+        "framework-tethering",
     ],
     sdk_version: "core_platform",
     apex_available: ["//apex_available:platform"],
diff --git a/StubLibraries.bp b/StubLibraries.bp
index d195047..baa3c61 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -41,10 +41,9 @@
 ]
 
 stubs_defaults {
-    name: "metalava-api-stubs-default",
+    name: "metalava-non-updatable-api-stubs-default",
     srcs: [
         ":framework-non-updatable-sources",
-        ":framework-updatable-sources",
         "core/java/**/*.logtags",
         ":opt-telephony-srcs",
         ":opt-net-voip-srcs",
@@ -64,14 +63,23 @@
         "sdk-dir",
         "api-versions-jars-dir",
     ],
-    sdk_version: "core_platform",
     filter_packages: packages_to_document,
 }
 
+stubs_defaults {
+    name: "metalava-api-stubs-default",
+    defaults: ["metalava-non-updatable-api-stubs-default"],
+    srcs: [":framework-updatable-sources"],
+    sdk_version: "core_platform",
+}
+
 /////////////////////////////////////////////////////////////////////
 // *-api-stubs-docs modules providing source files for the stub libraries
 /////////////////////////////////////////////////////////////////////
 
+// api-stubs-docs, system-api-stubs-docs, and test-api-stubs-docs have APIs
+// from the non-updatable part of the platform as well as from the updatable
+// modules
 droidstubs {
     name: "api-stubs-docs",
     defaults: ["metalava-api-stubs-default"],
@@ -112,7 +120,10 @@
     arg_files: [
         "core/res/AndroidManifest.xml",
     ],
-    args: metalava_framework_docs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\)",
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)",
     check_api: {
         current: {
             api_file: "api/system-current.txt",
@@ -155,6 +166,111 @@
 }
 
 /////////////////////////////////////////////////////////////////////
+// Following droidstubs modules are for extra APIs for modules.
+// The framework currently have two more API surfaces for modules:
+// @SystemApi(client=MODULE_APPS) and @SystemApi(client=MODULE_LIBRARIES)
+/////////////////////////////////////////////////////////////////////
+
+// TODO(b/146727827) remove the *-api modules when we can teach metalava
+// about the relationship among the API surfaces. Currently, these modules are only to generate
+// the API signature files and ensure that the APIs evolve in a backwards compatible manner.
+// They however are NOT used for building the API stub.
+droidstubs {
+    name: "module-app-api",
+    defaults: ["metalava-non-updatable-api-stubs-default"],
+    libs: ["framework-all"],
+    arg_files: ["core/res/AndroidManifest.xml"],
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)",
+    check_api: {
+        current: {
+            api_file: "api/module-app-current.txt",
+            removed_api_file: "api/module-app-removed.txt",
+        },
+        // TODO(b/147559833) enable the compatibility check against the last release API
+        // and the API lint
+        //last_released: {
+        //    api_file: ":last-released-module-app-api",
+        //    removed_api_file: "api/module-app-removed.txt",
+        //    baseline_file: ":module-app-api-incompatibilities-with-last-released"
+        //},
+        //api_lint: {
+        //    enabled: true,
+        //    new_since: ":last-released-module-app-api",
+        //    baseline_file: "api/module-app-lint-baseline.txt",
+        //},
+    },
+    //jdiff_enabled: true,
+}
+
+droidstubs {
+    name: "module-lib-api",
+    defaults: ["metalava-non-updatable-api-stubs-default"],
+    libs: ["framework-all"],
+    arg_files: ["core/res/AndroidManifest.xml"],
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+        "process=android.annotation.SystemApi.Process.ALL\\)",
+    check_api: {
+        current: {
+            api_file: "api/module-lib-current.txt",
+            removed_api_file: "api/module-lib-removed.txt",
+        },
+        // TODO(b/147559833) enable the compatibility check against the last release API
+        // and the API lint
+        //last_released: {
+        //    api_file: ":last-released-module-lib-api",
+        //    removed_api_file: "api/module-lib-removed.txt",
+        //    baseline_file: ":module-lib-api-incompatibilities-with-last-released"
+        //},
+        //api_lint: {
+        //    enabled: true,
+        //    new_since: ":last-released-module-lib-api",
+        //    baseline_file: "api/module-lib-lint-baseline.txt",
+        //},
+    },
+    //jdiff_enabled: true,
+}
+
+// The following two droidstubs modules generate source files for the API stub libraries for
+// modules. Note that they not only include their own APIs but also other APIs that have
+// narrower scope. For example, module-lib-api-stubs-docs includes all @SystemApis not just
+// the ones with 'client=MODULE_LIBRARIES'.
+droidstubs {
+    name: "module-app-api-stubs-docs",
+    defaults: ["metalava-non-updatable-api-stubs-default"],
+    libs: ["framework-all"],
+    arg_files: ["core/res/AndroidManifest.xml"],
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)" +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)",
+}
+
+droidstubs {
+    name: "module-lib-api-stubs-docs",
+    defaults: ["metalava-non-updatable-api-stubs-default"],
+    libs: ["framework-all"],
+    arg_files: ["core/res/AndroidManifest.xml"],
+    args: metalava_framework_docs_args +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)" +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+        "process=android.annotation.SystemApi.Process.ALL\\)" +
+        " --show-annotation android.annotation.SystemApi\\(" +
+        "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+        "process=android.annotation.SystemApi.Process.ALL\\)",
+}
+
+/////////////////////////////////////////////////////////////////////
 // android_*_stubs_current modules are the stubs libraries compiled
 // from *-api-stubs-docs
 /////////////////////////////////////////////////////////////////////
@@ -169,7 +285,6 @@
     java_resources: [
         ":notices-for-framework-stubs",
     ],
-    sdk_version: "core_current",
     system_modules: "none",
     java_version: "1.8",
     compile_dex: true,
@@ -187,6 +302,7 @@
         "private-stub-annotations-jar",
     ],
     defaults: ["framework-stubs-default"],
+    sdk_version: "core_current",
 }
 
 java_library_static {
@@ -201,6 +317,7 @@
         "private-stub-annotations-jar",
     ],
     defaults: ["framework-stubs-default"],
+    sdk_version: "core_current",
 }
 
 java_library_static {
@@ -215,6 +332,37 @@
         "private-stub-annotations-jar",
     ],
     defaults: ["framework-stubs-default"],
+    sdk_version: "core_current",
+}
+
+java_library_static {
+    name: "framework_module_app_stubs_current",
+    srcs: [
+        ":module-app-api-stubs-docs",
+    ],
+    libs: [
+        "stub-annotations",
+        "framework-all",
+    ],
+    static_libs: [
+        "private-stub-annotations-jar",
+    ],
+    defaults: ["framework-stubs-default"],
+}
+
+java_library_static {
+    name: "framework_module_lib_stubs_current",
+    srcs: [
+        ":module-lib-api-stubs-docs",
+    ],
+    libs: [
+        "stub-annotations",
+        "framework-all",
+    ],
+    static_libs: [
+        "private-stub-annotations-jar",
+    ],
+    defaults: ["framework-stubs-default"],
 }
 
 /////////////////////////////////////////////////////////////////////
diff --git a/apex/Android.bp b/apex/Android.bp
index 56f7db2..abebfa3 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -37,6 +37,36 @@
 
 stubs_defaults {
     name: "framework-module-stubs-defaults-systemapi",
-    args: mainline_stubs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) ",
+    args: mainline_stubs_args +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+    "process=android.annotation.SystemApi.Process.ALL\\) ",
+    installable: false,
+}
+
+stubs_defaults {
+    name: "framework-module-stubs-defaults-module_apps_api",
+    args: mainline_stubs_args +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+    "process=android.annotation.SystemApi.Process.ALL\\) " +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+    "process=android.annotation.SystemApi.Process.ALL\\) ",
+    installable: false,
+}
+
+stubs_defaults {
+    name: "framework-module-stubs-defaults-module_libs_api",
+    args: mainline_stubs_args +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+    "process=android.annotation.SystemApi.Process.ALL\\) " +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+    "process=android.annotation.SystemApi.Process.ALL\\) " +
+    " --show-annotation android.annotation.SystemApi\\(" +
+    "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+    "process=android.annotation.SystemApi.Process.ALL\\) ",
     installable: false,
 }
diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp
index 3dc5a2c..60cc3be 100644
--- a/apex/appsearch/framework/Android.bp
+++ b/apex/appsearch/framework/Android.bp
@@ -26,26 +26,14 @@
   installable: true,
   sdk_version: "core_platform", // TODO(b/146218515) should be core_current
   srcs: [":framework-appsearch-sources"],
+  hostdex: true, // for hiddenapi check
   libs: [
     "framework-minus-apex",  // TODO(b/146218515) should be framework-system-stubs
   ],
+  visibility: ["//frameworks/base/apex/appsearch:__subpackages__"],
+  apex_available: ["com.android.appsearch"],
 }
 
-metalava_appsearch_docs_args =
-    "--hide-package com.android.server " +
-    "--error UnhiddenSystemApi " +
-    "--hide RequiresPermission " +
-    "--hide MissingPermission " +
-    "--hide BroadcastBehavior " +
-    "--hide HiddenSuperclass " +
-    "--hide DeprecationMismatch " +
-    "--hide UnavailableSymbol " +
-    "--hide SdkConstant " +
-    "--hide HiddenTypeParameter " +
-    "--hide Todo --hide Typo " +
-    "--hide HiddenTypedefConstant " +
-    "--show-annotation android.annotation.SystemApi "
-
 droidstubs {
     name: "framework-appsearch-stubs-srcs",
     srcs: [
@@ -55,9 +43,8 @@
     aidl: {
         include_dirs: ["frameworks/base/core/java"],
     },
-    args: metalava_appsearch_docs_args,
-    sdk_version: "core_current",
-    libs: ["android_system_stubs_current"],
+    defaults: ["framework-module-stubs-defaults-systemapi"],
+    sdk_version: "system_current",
 }
 
 java_library {
@@ -68,7 +55,6 @@
             "java",
         ],
     },
-    sdk_version: "core_current",
-    libs: ["android_system_stubs_current"],
+    sdk_version: "system_current",
     installable: false,
 }
diff --git a/apex/appsearch/service/Android.bp b/apex/appsearch/service/Android.bp
index 4ebafce8..e7abcd9 100644
--- a/apex/appsearch/service/Android.bp
+++ b/apex/appsearch/service/Android.bp
@@ -20,8 +20,10 @@
   libs: [
     "framework",
     "services.core",
+    "framework-appsearch",
   ],
   static_libs: [
     "icing-java-proto-lite",
-  ]
+  ],
+  apex_available: [ "com.android.appsearch" ],
 }
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index 6bd0086..18382a4 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -55,6 +55,13 @@
     jarjar_rules: "jarjar_rules.txt",
 
     plugins: ["java_api_finder"],
+
+    hostdex: true, // for hiddenapi check
+    visibility: ["//frameworks/av/apex:__subpackages__"],
+    apex_available: [
+        "com.android.media",
+        "test_com.android.media",
+    ],
 }
 
 filegroup {
diff --git a/apex/sdkextensions/framework/Android.bp b/apex/sdkextensions/framework/Android.bp
index 5504f4e..dd17473 100644
--- a/apex/sdkextensions/framework/Android.bp
+++ b/apex/sdkextensions/framework/Android.bp
@@ -36,6 +36,11 @@
         "//frameworks/base/apex/sdkextensions",
         "//frameworks/base/apex/sdkextensions/testing",
     ],
+    hostdex: true, // for hiddenapi check
+    apex_available: [
+        "com.android.sdkext",
+        "test_com.android.sdkext",
+    ],
 }
 
 droidstubs {
diff --git a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
index 21b7767..99b9d39 100644
--- a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
+++ b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
@@ -67,11 +67,4 @@
 
     /** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */
     oneway void triggerUidSnapshot();
-
-    /** Tells StatsCompanionService to tell statsd to register a puller for the given atom id */
-    oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
-            in int[] additiveFields, IPullAtomCallback pullerCallback);
-
-    /** Tells StatsCompanionService to tell statsd to unregister a puller for the given atom id */
-    oneway void unregisterPullAtomCallback(int atomTag);
 }
diff --git a/apex/statsd/aidl/android/os/IStatsManagerService.aidl b/apex/statsd/aidl/android/os/IStatsManagerService.aidl
index dec5634..4a259f5 100644
--- a/apex/statsd/aidl/android/os/IStatsManagerService.aidl
+++ b/apex/statsd/aidl/android/os/IStatsManagerService.aidl
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.app.PendingIntent;
+import android.os.IPullAtomCallback;
 
 /**
   * Binder interface to communicate with the Java-based statistics service helper.
@@ -125,4 +126,11 @@
      * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
      */
     void removeConfiguration(in long configId, in String packageName);
-}
\ No newline at end of file
+
+    /** Tell StatsManagerService to register a puller for the given atom tag with statsd. */
+    oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
+            in int[] additiveFields, IPullAtomCallback pullerCallback);
+
+    /** Tell StatsManagerService to unregister the pulller for the given atom tag from statsd. */
+    oneway void unregisterPullAtomCallback(int atomTag);
+}
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
index a2b0577..0b46645a 100644
--- a/apex/statsd/framework/Android.bp
+++ b/apex/statsd/framework/Android.bp
@@ -37,7 +37,16 @@
         // TODO(b/146230220): Use framework-system-stubs instead.
         "android_system_stubs_current",
     ],
-    // TODO:(b/146210774): Add apex_available field.
+    hostdex: true, // for hiddenapi check
+    visibility: [
+        "//frameworks/base/apex/statsd:__subpackages__",
+        //TODO(b/146167933) remove this when framework is built with framework-statsd-stubs
+        "//frameworks/base",
+    ],
+    apex_available: [
+        "com.android.os.statsd",
+        "test_com.android.os.statsd",
+    ],
 }
 
 droidstubs {
diff --git a/apex/statsd/service/Android.bp b/apex/statsd/service/Android.bp
index f3a8989..9103848 100644
--- a/apex/statsd/service/Android.bp
+++ b/apex/statsd/service/Android.bp
@@ -13,4 +13,8 @@
         "framework-minus-apex",
         "services.core",
     ],
+    apex_available: [
+        "com.android.os.statsd",
+        "test_com.android.os.statsd",
+    ],
 }
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index d57afee..0f981e2 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -75,7 +75,6 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.IPullAtomCallback;
 import android.os.IStatsCompanionService;
 import android.os.IStatsd;
 import android.os.IStoraged;
@@ -263,71 +262,6 @@
 
     private StatsManagerService mStatsManagerService;
 
-    private static final class PullerKey {
-        private final int mUid;
-        private final int mAtomTag;
-
-        PullerKey(int uid, int atom) {
-            mUid = uid;
-            mAtomTag = atom;
-        }
-
-        public int getUid() {
-            return mUid;
-        }
-
-        public int getAtom() {
-            return mAtomTag;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mUid, mAtomTag);
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj instanceof PullerKey) {
-                PullerKey other = (PullerKey) obj;
-                return this.mUid == other.getUid() && this.mAtomTag == other.getAtom();
-            }
-            return false;
-        }
-    }
-
-    private static final class PullerValue {
-        private final long mCoolDownNs;
-        private final long mTimeoutNs;
-        private int[] mAdditiveFields;
-        private IPullAtomCallback mCallback;
-
-        PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields,
-                IPullAtomCallback callback) {
-            mCoolDownNs = coolDownNs;
-            mTimeoutNs = timeoutNs;
-            mAdditiveFields = additiveFields;
-            mCallback = callback;
-        }
-
-        public long getCoolDownNs() {
-            return mCoolDownNs;
-        }
-
-        public long getTimeoutNs() {
-            return mTimeoutNs;
-        }
-
-        public int[] getAdditiveFields() {
-            return mAdditiveFields;
-        }
-
-        public IPullAtomCallback getCallback() {
-            return mCallback;
-        }
-    }
-
-    private final HashMap<PullerKey, PullerValue> mPullers = new HashMap<>();
-
     private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
     private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
     private WifiManager mWifiManager = null;
@@ -881,31 +815,6 @@
         }
     }
 
-    private void pullWifiBytesTransfer(
-            int tagId, long elapsedNanos, long wallClockNanos,
-            List<StatsLogEventWrapper> pulledData) {
-        long token = Binder.clearCallingIdentity();
-        try {
-            // TODO: Consider caching the following call to get BatteryStatsInternal.
-            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
-            String[] ifaces = bs.getWifiIfaces();
-            if (ifaces.length == 0) {
-                return;
-            }
-            if (mNetworkStatsService == null) {
-                Slog.e(TAG, "NetworkStats Service is not available!");
-                return;
-            }
-            // Combine all the metrics per Uid into one record.
-            NetworkStats stats = mNetworkStatsService.getDetailedUidStats(ifaces).groupedByUid();
-            addNetworkStats(tagId, pulledData, stats, false);
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Pulling netstats for wifi bytes has error", e);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
     private void pullWifiBytesTransferByFgBg(
             int tagId, long elapsedNanos, long wallClockNanos,
             List<StatsLogEventWrapper> pulledData) {
@@ -2382,149 +2291,180 @@
         long elapsedNanos = SystemClock.elapsedRealtimeNanos();
         long wallClockNanos = SystemClock.currentTimeMicro() * 1000L;
         switch (tagId) {
-            case StatsLog.WIFI_BYTES_TRANSFER: {
-                pullWifiBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret);
-                break;
-            }
+            
             case StatsLog.MOBILE_BYTES_TRANSFER: {
                 pullMobileBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: {
                 pullWifiBytesTransferByFgBg(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: {
                 pullMobileBytesTransferByFgBg(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.BLUETOOTH_BYTES_TRANSFER: {
                 pullBluetoothBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.KERNEL_WAKELOCK: {
                 pullKernelWakelock(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.CPU_TIME_PER_FREQ: {
                 pullCpuTimePerFreq(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.CPU_TIME_PER_UID: {
                 pullKernelUidCpuTime(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.CPU_TIME_PER_UID_FREQ: {
                 pullKernelUidCpuFreqTime(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.CPU_CLUSTER_TIME: {
                 pullKernelUidCpuClusterTime(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.CPU_ACTIVE_TIME: {
                 pullKernelUidCpuActiveTime(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.WIFI_ACTIVITY_INFO: {
                 pullWifiActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.MODEM_ACTIVITY_INFO: {
                 pullModemActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.BLUETOOTH_ACTIVITY_INFO: {
                 pullBluetoothActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.SYSTEM_UPTIME: {
                 pullSystemUpTime(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.SYSTEM_ELAPSED_REALTIME: {
                 pullSystemElapsedRealtime(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.PROCESS_MEMORY_STATE: {
                 pullProcessMemoryState(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.PROCESS_MEMORY_HIGH_WATER_MARK: {
                 pullProcessMemoryHighWaterMark(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.PROCESS_MEMORY_SNAPSHOT: {
                 pullProcessMemorySnapshot(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.SYSTEM_ION_HEAP_SIZE: {
                 pullSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.PROCESS_SYSTEM_ION_HEAP_SIZE: {
                 pullProcessSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.BINDER_CALLS: {
                 pullBinderCallsStats(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.BINDER_CALLS_EXCEPTIONS: {
                 pullBinderCallsStatsExceptions(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.LOOPER_STATS: {
                 pullLooperStats(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DISK_STATS: {
                 pullDiskStats(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DIRECTORY_USAGE: {
                 pullDirectoryUsage(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.APP_SIZE: {
                 pullAppSize(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.CATEGORY_SIZE: {
                 pullCategorySize(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.NUM_FINGERPRINTS_ENROLLED: {
                 pullNumBiometricsEnrolled(BiometricsProtoEnums.MODALITY_FINGERPRINT, tagId,
                         elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.NUM_FACES_ENROLLED: {
                 pullNumBiometricsEnrolled(BiometricsProtoEnums.MODALITY_FACE, tagId, elapsedNanos,
                         wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.PROC_STATS: {
                 pullProcessStats(ProcessStats.REPORT_ALL, tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.PROC_STATS_PKG_PROC: {
                 pullProcessStats(ProcessStats.REPORT_PKG_PROC_STATS, tagId, elapsedNanos,
                         wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DISK_IO: {
                 pullDiskIo(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.POWER_PROFILE: {
                 pullPowerProfile(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.BUILD_INFORMATION: {
                 pullBuildInformation(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.PROCESS_CPU_TIME: {
                 pullProcessCpuTime(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
@@ -2533,73 +2473,90 @@
                 pullCpuTimePerThreadFreq(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DEVICE_CALCULATED_POWER_USE: {
                 pullDeviceCalculatedPowerUse(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DEVICE_CALCULATED_POWER_BLAME_UID: {
                 pullDeviceCalculatedPowerBlameUid(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER: {
                 pullDeviceCalculatedPowerBlameOther(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.TEMPERATURE: {
                 pullTemperature(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.COOLING_DEVICE: {
                 pullCoolingDevices(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DEBUG_ELAPSED_CLOCK: {
                 pullDebugElapsedClock(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DEBUG_FAILING_ELAPSED_CLOCK: {
                 pullDebugFailingElapsedClock(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.ROLE_HOLDER: {
                 pullRoleHolders(elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DANGEROUS_PERMISSION_STATE: {
                 pullDangerousPermissionState(StatsLog.DANGEROUS_PERMISSION_STATE, elapsedNanos,
                         wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED: {
                 pullDangerousPermissionState(StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED,
                         elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.TIME_ZONE_DATA_INFO: {
                 pullTimeZoneDataInfo(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.EXTERNAL_STORAGE_INFO: {
                 pullExternalStorageInfo(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.APPS_ON_EXTERNAL_STORAGE_INFO: {
                 pullAppsOnExternalStorageInfo(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.FACE_SETTINGS: {
                 pullFaceSettings(tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.APP_OPS: {
                 pullAppOps(elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             case StatsLog.NOTIFICATION_REMOTE_VIEWS: {
                 pullNotificationStats(NotificationManagerService.REPORT_REMOTE_VIEWS,
                         tagId, elapsedNanos, wallClockNanos, ret);
                 break;
             }
+
             default:
                 Slog.w(TAG, "No such tagId data as " + tagId);
                 return null;
@@ -2634,57 +2591,6 @@
         }
     }
 
-    @Override
-    public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
-            int[] additiveFields, IPullAtomCallback pullerCallback) {
-        synchronized (sStatsdLock) {
-            // Always cache the puller in SCS.
-            // If statsd is down, we will register it when it comes back up.
-            int callingUid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            PullerKey key = new PullerKey(callingUid, atomTag);
-            PullerValue val = new PullerValue(
-                    coolDownNs, timeoutNs, additiveFields, pullerCallback);
-            mPullers.put(key, val);
-
-            if (sStatsd == null) {
-                Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag);
-                return;
-            }
-            try {
-                sStatsd.registerPullAtomCallback(
-                        callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-    }
-
-    @Override
-    public void unregisterPullAtomCallback(int atomTag) {
-        synchronized (sStatsdLock) {
-            // Always remove the puller in SCS.
-            // If statsd is down, we will not register it when it comes back up.
-            int callingUid = Binder.getCallingUid();
-            final long token = Binder.clearCallingIdentity();
-            PullerKey key = new PullerKey(callingUid, atomTag);
-            mPullers.remove(key);
-
-            if (sStatsd == null) {
-                Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag);
-                return;
-            }
-            try {
-                sStatsd.unregisterPullAtomCallback(callingUid, atomTag);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-    }
 
     // Statsd related code
 
@@ -2763,8 +2669,6 @@
                     // Pull the latest state of UID->app name, version mapping when
                     // statsd starts.
                     informAllUidsLocked(mContext);
-                    // Register all pullers. If SCS has just started, this should be empty.
-                    registerAllPullersLocked();
                 } finally {
                     restoreCallingIdentity(token);
                 }
@@ -2776,17 +2680,6 @@
         }
     }
 
-    @GuardedBy("sStatsdLock")
-    private void registerAllPullersLocked() throws RemoteException {
-        // TODO: pass in one call, using a file descriptor (similar to uidmap).
-        for (Map.Entry<PullerKey, PullerValue> entry : mPullers.entrySet()) {
-            PullerKey key = entry.getKey();
-            PullerValue val = entry.getValue();
-            sStatsd.registerPullAtomCallback(key.getUid(), key.getAtom(), val.getCoolDownNs(),
-                    val.getTimeoutNs(), val.getAdditiveFields(), val.getCallback());
-        }
-    }
-
     private class StatsdDeathRecipient implements IBinder.DeathRecipient {
         @Override
         public void binderDied() {
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
index b27d0f7..04d8b00 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
@@ -24,6 +24,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.os.Binder;
+import android.os.IPullAtomCallback;
 import android.os.IStatsManagerService;
 import android.os.IStatsd;
 import android.os.Process;
@@ -60,8 +61,7 @@
     @GuardedBy("mLock")
     private ArrayMap<ConfigKey, PendingIntentRef> mDataFetchPirMap = new ArrayMap<>();
     @GuardedBy("mLock")
-    private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap =
-            new ArrayMap<>();
+    private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap = new ArrayMap<>();
     @GuardedBy("mLock")
     private ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> mBroadcastSubscriberPirMap =
             new ArrayMap<>();
@@ -72,8 +72,8 @@
     }
 
     private static class ConfigKey {
-        private int mUid;
-        private long mConfigId;
+        private final int mUid;
+        private final long mConfigId;
 
         ConfigKey(int uid, long configId) {
             mUid = uid;
@@ -103,6 +103,126 @@
         }
     }
 
+    private static class PullerKey {
+        private final int mUid;
+        private final int mAtomTag;
+
+        PullerKey(int uid, int atom) {
+            mUid = uid;
+            mAtomTag = atom;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public int getAtom() {
+            return mAtomTag;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mUid, mAtomTag);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof PullerKey) {
+                PullerKey other = (PullerKey) obj;
+                return this.mUid == other.getUid() && this.mAtomTag == other.getAtom();
+            }
+            return false;
+        }
+    }
+
+    private static class PullerValue {
+        private final long mCoolDownNs;
+        private final long mTimeoutNs;
+        private final int[] mAdditiveFields;
+        private final IPullAtomCallback mCallback;
+
+        PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields,
+                IPullAtomCallback callback) {
+            mCoolDownNs = coolDownNs;
+            mTimeoutNs = timeoutNs;
+            mAdditiveFields = additiveFields;
+            mCallback = callback;
+        }
+
+        public long getCoolDownNs() {
+            return mCoolDownNs;
+        }
+
+        public long getTimeoutNs() {
+            return mTimeoutNs;
+        }
+
+        public int[] getAdditiveFields() {
+            return mAdditiveFields;
+        }
+
+        public IPullAtomCallback getCallback() {
+            return mCallback;
+        }
+    }
+
+    private final ArrayMap<PullerKey, PullerValue> mPullers = new ArrayMap<>();
+
+    @Override
+    public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
+            int[] additiveFields, IPullAtomCallback pullerCallback) {
+        int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        PullerKey key = new PullerKey(callingUid, atomTag);
+        PullerValue val = new PullerValue(coolDownNs, timeoutNs, additiveFields, pullerCallback);
+
+        // Always cache the puller in StatsManagerService. If statsd is down, we will register the
+        // puller when statsd comes back up.
+        synchronized (mLock) {
+            mPullers.put(key, val);
+        }
+
+        IStatsd statsd = getStatsdNonblocking();
+        if (statsd == null) {
+            return;
+        }
+
+        try {
+            statsd.registerPullAtomCallback(
+                    callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void unregisterPullAtomCallback(int atomTag) {
+        int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        PullerKey key = new PullerKey(callingUid, atomTag);
+
+        // Always remove the puller from StatsManagerService even if statsd is down. When statsd
+        // comes back up, we will not re-register the removed puller.
+        synchronized (mLock) {
+            mPullers.remove(key);
+        }
+
+        IStatsd statsd = getStatsdNonblocking();
+        if (statsd == null) {
+            return;
+        }
+
+        try {
+            statsd.unregisterPullAtomCallback(callingUid, atomTag);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     @Override
     public void setDataFetchOperation(long configId, PendingIntent pendingIntent,
             String packageName) {
@@ -441,46 +561,85 @@
         if (statsd == null) {
             return;
         }
-        // Since we do not want to make an IPC with the a lock held, we first create local deep
-        // copies of the data with the lock held before iterating through the maps.
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            registerAllPullers(statsd);
+            registerAllDataFetchOperations(statsd);
+            registerAllActiveConfigsChangedOperations(statsd);
+            registerAllBroadcastSubscribers(statsd);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "StatsManager failed to (re-)register data with statsd");
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Pre-condition: the Binder calling identity has already been cleared
+    private void registerAllPullers(IStatsd statsd) throws RemoteException {
+        // Since we do not want to make an IPC with the lock held, we first create a copy of the
+        // data with the lock held before iterating through the map.
+        ArrayMap<PullerKey, PullerValue> pullersCopy;
+        synchronized (mLock) {
+            pullersCopy = new ArrayMap<>(mPullers);
+        }
+
+        for (Map.Entry<PullerKey, PullerValue> entry : pullersCopy.entrySet()) {
+            PullerKey key = entry.getKey();
+            PullerValue value = entry.getValue();
+            statsd.registerPullAtomCallback(key.getUid(), key.getAtom(), value.getCoolDownNs(),
+                    value.getTimeoutNs(), value.getAdditiveFields(), value.getCallback());
+        }
+    }
+
+    // Pre-condition: the Binder calling identity has already been cleared
+    private void registerAllDataFetchOperations(IStatsd statsd) throws RemoteException {
+        // Since we do not want to make an IPC with the lock held, we first create a copy of the
+        // data with the lock held before iterating through the map.
         ArrayMap<ConfigKey, PendingIntentRef> dataFetchCopy;
-        ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy;
-        ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy;
         synchronized (mLock) {
             dataFetchCopy = new ArrayMap<>(mDataFetchPirMap);
-            activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap);
-            broadcastSubscriberCopy = new ArrayMap<>();
-            for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry
-                    : mBroadcastSubscriberPirMap.entrySet()) {
-                broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap<>(entry.getValue()));
-            }
         }
+
         for (Map.Entry<ConfigKey, PendingIntentRef> entry : dataFetchCopy.entrySet()) {
             ConfigKey key = entry.getKey();
-            try {
-                statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid());
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to setDataFetchOperation from pirMap");
+            statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid());
+        }
+    }
+
+    // Pre-condition: the Binder calling identity has already been cleared
+    private void registerAllActiveConfigsChangedOperations(IStatsd statsd) throws RemoteException {
+        // Since we do not want to make an IPC with the lock held, we first create a copy of the
+        // data with the lock held before iterating through the map.
+        ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy;
+        synchronized (mLock) {
+            activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap);
+        }
+
+        for (Map.Entry<Integer, PendingIntentRef> entry : activeConfigsChangedCopy.entrySet()) {
+            statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey());
+        }
+    }
+
+    // Pre-condition: the Binder calling identity has already been cleared
+    private void registerAllBroadcastSubscribers(IStatsd statsd) throws RemoteException {
+        // Since we do not want to make an IPC with the lock held, we first create a deep copy of
+        // the data with the lock held before iterating through the map.
+        ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy =
+                new ArrayMap<>();
+        synchronized (mLock) {
+            for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry :
+                    mBroadcastSubscriberPirMap.entrySet()) {
+                broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap(entry.getValue()));
             }
         }
-        for (Map.Entry<Integer, PendingIntentRef> entry
-                : activeConfigsChangedCopy.entrySet()) {
-            try {
-                statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey());
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to setActiveConfigsChangedOperation from pirMap");
-            }
-        }
-        for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry
-                : broadcastSubscriberCopy.entrySet()) {
+
+        for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry :
+                mBroadcastSubscriberPirMap.entrySet()) {
+            ConfigKey configKey = entry.getKey();
             for (Map.Entry<Long, PendingIntentRef> subscriberEntry : entry.getValue().entrySet()) {
-                ConfigKey configKey = entry.getKey();
-                try {
-                    statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(),
-                            subscriberEntry.getValue(), configKey.getUid());
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Failed to setBroadcastSubscriber from pirMap");
-                }
+                statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(),
+                        subscriberEntry.getValue(), configKey.getUid());
             }
         }
     }
diff --git a/api/current.txt b/api/current.txt
index 04a4946..5ab1c01 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -115,6 +115,7 @@
     field public static final String READ_LOGS = "android.permission.READ_LOGS";
     field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
     field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+    field public static final String READ_PRECISE_PHONE_STATE = "android.permission.READ_PRECISE_PHONE_STATE";
     field public static final String READ_SMS = "android.permission.READ_SMS";
     field public static final String READ_SYNC_SETTINGS = "android.permission.READ_SYNC_SETTINGS";
     field public static final String READ_SYNC_STATS = "android.permission.READ_SYNC_STATS";
@@ -1144,6 +1145,7 @@
     field public static final int resizeable = 16843405; // 0x101028d
     field public static final int resizeableActivity = 16844022; // 0x10104f6
     field public static final int resource = 16842789; // 0x1010025
+    field public static final int resourcesMap = 16844297; // 0x1010609
     field public static final int restoreAnyVersion = 16843450; // 0x10102ba
     field @Deprecated public static final int restoreNeedsApplication = 16843421; // 0x101029d
     field public static final int restrictedAccountType = 16843733; // 0x10103d5
@@ -2923,6 +2925,7 @@
     method public int getShowMode();
     method public boolean removeOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
     method public boolean setShowMode(int);
+    method public boolean switchToInputMethod(@NonNull String);
   }
 
   public static interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener {
@@ -5837,6 +5840,7 @@
     method public void enableLights(boolean);
     method public void enableVibration(boolean);
     method public android.media.AudioAttributes getAudioAttributes();
+    method @Nullable public String getConversationId();
     method public String getDescription();
     method public String getGroup();
     method public String getId();
@@ -5844,12 +5848,14 @@
     method public int getLightColor();
     method public int getLockscreenVisibility();
     method public CharSequence getName();
+    method @Nullable public String getParentChannelId();
     method public android.net.Uri getSound();
     method public long[] getVibrationPattern();
     method public boolean hasUserSetImportance();
     method public boolean hasUserSetSound();
     method public void setAllowBubbles(boolean);
     method public void setBypassDnd(boolean);
+    method public void setConversationId(@Nullable String, @Nullable String);
     method public void setDescription(String);
     method public void setGroup(String);
     method public void setImportance(int);
@@ -5862,6 +5868,7 @@
     method public boolean shouldShowLights();
     method public boolean shouldVibrate();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s";
     field @NonNull public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
     field public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
   }
@@ -5903,6 +5910,7 @@
     method public final int getCurrentInterruptionFilter();
     method public int getImportance();
     method public android.app.NotificationChannel getNotificationChannel(String);
+    method @Nullable public android.app.NotificationChannel getNotificationChannel(@NonNull String, @NonNull String);
     method public android.app.NotificationChannelGroup getNotificationChannelGroup(String);
     method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
     method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
@@ -9823,6 +9831,7 @@
     method public boolean bindIsolatedService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection);
     method public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int);
     method public boolean bindService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection);
+    method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.INTERACT_ACROSS_PROFILES}) public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle);
     method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String);
     method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int);
     method @CheckResult(suggest="#enforceCallingPermission(String,String)") public abstract int checkCallingPermission(@NonNull String);
@@ -10000,7 +10009,6 @@
     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
@@ -11358,6 +11366,7 @@
   public class CrossProfileApps {
     method public boolean canInteractAcrossProfiles();
     method public boolean canRequestInteractAcrossProfiles();
+    method @Nullable public android.content.Intent createRequestInteractAcrossProfilesIntent();
     method @NonNull public android.graphics.drawable.Drawable getProfileSwitchingIconDrawable(@NonNull android.os.UserHandle);
     method @NonNull public CharSequence getProfileSwitchingLabel(@NonNull android.os.UserHandle);
     method @NonNull public java.util.List<android.os.UserHandle> getTargetUserProfiles();
@@ -23655,6 +23664,7 @@
     field public static final int TYPE_BUILTIN_EARPIECE = 1; // 0x1
     field public static final int TYPE_BUILTIN_MIC = 15; // 0xf
     field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2
+    field public static final int TYPE_BUILTIN_SPEAKER_SAFE = 24; // 0x18
     field public static final int TYPE_BUS = 21; // 0x15
     field public static final int TYPE_DOCK = 13; // 0xd
     field public static final int TYPE_FM = 14; // 0xe
@@ -26288,6 +26298,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);
@@ -26411,6 +26474,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();
@@ -26769,6 +26846,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);
@@ -28693,7 +28786,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
@@ -28702,21 +28795,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 {
@@ -30657,11 +30750,11 @@
     ctor public WifiNetworkSuggestion.Builder();
     method @NonNull public android.net.wifi.WifiNetworkSuggestion build();
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setBssid(@NonNull android.net.MacAddress);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setCredentialSharedWithUser(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsAppInteractionRequired(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsEnhancedOpen(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsHiddenSsid(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsMetered(boolean);
-    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsUserAllowedToManuallyConnect(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsUserInteractionRequired(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPasspointConfig(@NonNull android.net.wifi.hotspot2.PasspointConfiguration);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriority(@IntRange(from=0) int);
@@ -35831,6 +35924,7 @@
     field public static final int THREAD_PRIORITY_URGENT_AUDIO = -19; // 0xffffffed
     field public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 0xfffffff8
     field public static final int THREAD_PRIORITY_VIDEO = -10; // 0xfffffff6
+    field public static final int WIFI_UID = 1010; // 0x3f2
   }
 
   public abstract class ProxyFileDescriptorCallback {
@@ -36065,6 +36159,7 @@
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
     method public boolean hasUserRestriction(String);
     method public boolean isDemoUser();
+    method public boolean isManagedProfile();
     method public boolean isQuietModeEnabled(android.os.UserHandle);
     method public boolean isSystemUser();
     method public boolean isUserAGoat();
@@ -39333,6 +39428,7 @@
     field @Deprecated public static final String LONGITUDE = "longitude";
     field @Deprecated public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
     field @Deprecated public static final String PICASA_ID = "picasa_id";
+    field public static final String SCENE_CAPTURE_TYPE = "scene_capture_type";
   }
 
   public static final class MediaStore.Images.Media implements android.provider.MediaStore.Images.ImageColumns {
@@ -42674,11 +42770,20 @@
     method public android.content.Intent createEnrollIntent();
     method public android.content.Intent createReEnrollIntent();
     method public android.content.Intent createUnEnrollIntent();
+    method public int getParameter(int);
+    method public int getSupportedAudioCapabilities();
     method public int getSupportedRecognitionModes();
+    method @Nullable public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int);
+    method public int setParameter(int, int);
     method public boolean startRecognition(int);
     method public boolean stopRecognition();
+    field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
+    field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
+    field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
     field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
     field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
+    field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4
+    field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8
     field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2
     field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1
     field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe
@@ -42701,6 +42806,11 @@
     method @Nullable public byte[] getTriggerAudio();
   }
 
+  public static final class AlwaysOnHotwordDetector.ModelParamRange {
+    method public int end();
+    method public int start();
+  }
+
   public class VoiceInteractionService extends android.app.Service {
     ctor public VoiceInteractionService();
     method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
@@ -45523,11 +45633,6 @@
     method @Nullable public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, @NonNull java.util.concurrent.Executor, android.telephony.mbms.StreamingServiceCallback);
   }
 
-  public final 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);
@@ -45765,8 +45870,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 @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 void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent);
+    method @Nullable 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);
@@ -45775,7 +45880,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 @Deprecated public void sendMultimediaMessage(android.content.Context, android.net.Uri, String, android.os.Bundle, android.app.PendingIntent);
+    method 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);
@@ -51607,9 +51712,10 @@
     method public boolean dispatchUnhandledMove(android.view.View, int);
     method protected void dispatchVisibilityChanged(@NonNull android.view.View, int);
     method public void dispatchWindowFocusChanged(boolean);
-    method public void dispatchWindowInsetsAnimationFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+    method public void dispatchWindowInsetsAnimationFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+    method public void dispatchWindowInsetsAnimationPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
     method @NonNull public android.view.WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull android.view.WindowInsets);
-    method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
+    method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
     method public void dispatchWindowSystemUiVisiblityChanged(int);
     method public void dispatchWindowVisibilityChanged(int);
     method @CallSuper public void draw(android.graphics.Canvas);
@@ -53289,9 +53395,10 @@
   }
 
   public interface WindowInsetsAnimationCallback {
-    method public default void onFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+    method public default void onFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+    method public default void onPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
     method @NonNull public android.view.WindowInsets onProgress(@NonNull android.view.WindowInsets);
-    method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
+    method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
   }
 
   public static final class WindowInsetsAnimationCallback.AnimationBounds {
diff --git a/api/module-app-current.txt b/api/module-app-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-app-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/module-app-removed.txt b/api/module-app-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-app-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-lib-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/module-lib-removed.txt b/api/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/system-current.txt b/api/system-current.txt
index 5a9ef1b..a7d5b79 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -152,6 +152,7 @@
     field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
     field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
     field public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES";
+    field public static final String RADIO_SCAN_WITHOUT_LOCATION = "android.permission.RADIO_SCAN_WITHOUT_LOCATION";
     field public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission.READ_ACTIVE_EMERGENCY_SESSION";
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
     field public static final String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS";
@@ -189,6 +190,7 @@
     field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES";
     field public static final String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS";
     field public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
+    field public static final String SECURE_ELEMENT_PRIVILEGED = "android.permission.SECURE_ELEMENT_PRIVILEGED";
     field public static final String SEND_DEVICE_CUSTOMIZATION_READY = "android.permission.SEND_DEVICE_CUSTOMIZATION_READY";
     field public static final String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS";
     field public static final String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION";
@@ -240,7 +242,6 @@
     field public static final int isVrOnly = 16844152; // 0x1010578
     field public static final int requiredSystemPropertyName = 16844133; // 0x1010565
     field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566
-    field public static final int resourcesMap = 16844297; // 0x1010609
     field public static final int supportsAmbientMode = 16844173; // 0x101058d
     field public static final int userRestriction = 16844164; // 0x1010584
   }
@@ -812,6 +813,7 @@
     field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
     field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
     field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION";
+    field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
     field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
     field public static final String ACTION_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";
@@ -1535,6 +1537,10 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
   }
 
+  public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+  }
+
   public final class BluetoothHidHost implements android.bluetooth.BluetoothProfile {
     method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
@@ -1543,12 +1549,20 @@
     field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
   }
 
+  public final class BluetoothMap implements android.bluetooth.BluetoothProfile {
+    method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int);
+    field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
+  }
+
   public final class BluetoothPan implements android.bluetooth.BluetoothProfile {
     method protected void finalize();
     method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
     method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
     method public boolean isTetheringOn();
     method public void setBluetoothTethering(boolean);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
     field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
     field public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
     field public static final int LOCAL_NAP_ROLE = 1; // 0x1
@@ -1671,7 +1685,6 @@
   }
 
   public abstract class Context {
-    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean bindServiceAsUser(@RequiresPermission android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle);
     method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
     method public abstract android.content.Context createCredentialProtectedStorageContext();
     method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -1780,6 +1793,7 @@
     field @Deprecated public static final String EXTRA_SIM_STATE = "ss";
     field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP";
     field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE";
+    field public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 67108864; // 0x4000000
     field public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION";
     field @Deprecated public static final String SIM_ABSENT_ON_PERM_DISABLED = "PERM_DISABLED";
     field @Deprecated public static final String SIM_LOCKED_NETWORK = "NETWORK";
@@ -3545,7 +3559,6 @@
   }
 
   public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable {
-    method public int describeContents();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModelParamRange> CREATOR;
     field public final int end;
@@ -3555,7 +3568,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;
@@ -3566,6 +3582,7 @@
     field public final int powerConsumptionMw;
     field public final int recognitionModes;
     field public final boolean returnsTriggerInEvent;
+    field @NonNull public final String supportedModelArch;
     field public final boolean supportsCaptureTransition;
     field public final boolean supportsConcurrentCapture;
     field @NonNull public final java.util.UUID uuid;
@@ -4051,6 +4068,10 @@
     field public static final int ROLE_OUTPUT = 2; // 0x2
   }
 
+  public final class AudioDeviceInfo {
+    field public static final int TYPE_REMOTE_SUBMIX = 25; // 0x19
+  }
+
   public final class AudioFocusInfo implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.media.AudioAttributes getAttributes();
@@ -4078,6 +4099,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies();
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAddress> getDevicesForAttributes(@NonNull android.media.AudioAttributes);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAddress getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
@@ -4311,7 +4333,7 @@
   }
 
   public static interface MediaSessionManager.OnMediaKeyEventDispatchedListener {
-    method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @NonNull android.media.session.MediaSession.Token);
+    method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @Nullable android.media.session.MediaSession.Token);
   }
 
   public static interface MediaSessionManager.OnMediaKeyEventSessionChangedListener {
@@ -4345,6 +4367,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 {
@@ -4367,9 +4391,9 @@
     method public int getDetectionServiceOperationsTimeout();
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModelParamRange queryParameter(@Nullable java.util.UUID, int);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model);
   }
 
@@ -4594,6 +4618,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();
@@ -4616,6 +4662,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
   }
 
 }
@@ -5830,14 +5883,17 @@
 
   public final class SoftApConfiguration implements android.os.Parcelable {
     method public int describeContents();
+    method @NonNull public java.util.List<android.net.MacAddress> getAllowedClientList();
     method public int getBand();
+    method @NonNull public java.util.List<android.net.MacAddress> getBlockedClientList();
     method @Nullable public android.net.MacAddress getBssid();
     method public int getChannel();
     method public int getMaxNumberOfClients();
     method @Nullable public String getPassphrase();
     method public int getSecurityType();
+    method public int getShutdownTimeoutMillis();
     method @Nullable public String getSsid();
-    method @Nullable public String getWpa2Passphrase();
+    method public boolean isClientControlByUserEnabled();
     method public boolean isHiddenSsid();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int BAND_2GHZ = 1; // 0x1
@@ -5855,14 +5911,16 @@
     ctor public SoftApConfiguration.Builder();
     ctor public SoftApConfiguration.Builder(@NonNull android.net.wifi.SoftApConfiguration);
     method @NonNull public android.net.wifi.SoftApConfiguration build();
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder enableClientControlByUser(boolean);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBand(int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBssid(@Nullable android.net.MacAddress);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setChannel(int, int);
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setClientList(@NonNull java.util.List<android.net.MacAddress>, @NonNull java.util.List<android.net.MacAddress>);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setPassphrase(@Nullable String, int);
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setShutdownTimeoutMillis(int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setSsid(@Nullable String);
-    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setWpa2Passphrase(@Nullable String);
   }
 
   public final class SoftApInfo implements android.os.Parcelable {
@@ -6025,6 +6083,7 @@
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void getWifiActivityEnergyInfoAsync(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiActivityEnergyInfoListener);
     method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration();
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState();
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(@NonNull java.util.List<android.net.wifi.ScanResult>);
     method public boolean isApMacRandomizationSupported();
     method public boolean isConnectedMacRandomizationSupported();
     method @Deprecated public boolean isDeviceToDeviceRttSupported();
@@ -6095,6 +6154,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
@@ -6136,6 +6197,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);
@@ -6169,6 +6231,25 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int);
   }
 
+  public final class WifiOemConfigStoreMigrationHook {
+    method @Nullable public static android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData load();
+  }
+
+  public static final class WifiOemConfigStoreMigrationHook.MigrationData implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public java.util.List<android.net.wifi.WifiConfiguration> getUserSavedNetworkConfigurations();
+    method @Nullable public android.net.wifi.SoftApConfiguration getUserSoftApConfiguration();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData> CREATOR;
+  }
+
+  public static final class WifiOemConfigStoreMigrationHook.MigrationData.Builder {
+    ctor public WifiOemConfigStoreMigrationHook.MigrationData.Builder();
+    method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData build();
+    method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSavedNetworkConfigurations(@NonNull java.util.List<android.net.wifi.WifiConfiguration>);
+    method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration);
+  }
+
   public class WifiScanner {
     method @Deprecated public void configureWifiChange(int, int, int, int, int, android.net.wifi.WifiScanner.BssidInfo[]);
     method @Deprecated public void configureWifiChange(android.net.wifi.WifiScanner.WifiChangeSettings);
@@ -7271,7 +7352,6 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isAdminUser();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isGuestUser();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile(int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isPrimaryUser();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isRestrictedProfile();
@@ -7861,6 +7941,7 @@
   }
 
   public final class Settings {
+    method public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, boolean);
     field public static final String ACTION_ACCESSIBILITY_DETAILS_SETTINGS = "android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
     field public static final String ACTION_BUGREPORT_HANDLER_SETTINGS = "android.settings.BUGREPORT_HANDLER_SETTINGS";
     field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
@@ -8659,7 +8740,10 @@
     method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel);
     method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
     method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String);
+    method public void onNotificationVisibilityChanged(@NonNull String, boolean);
     method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>);
+    method public void onPanelHidden();
+    method public void onPanelRevealed(int);
     method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
     method public final void unsnoozeNotification(@NonNull String);
     field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
@@ -10380,6 +10464,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode();
     method public int getEmergencyNumberDbVersion();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getIsimImpu();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
     method public int getMaxNumberOfSimultaneouslyActiveSims();
@@ -10408,6 +10493,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int);
     method public boolean isCurrentSimOperator(@NonNull String, int, @Nullable String);
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataConnectionEnabled();
     method public boolean isDataConnectivityPossible();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
@@ -10468,7 +10554,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";
@@ -11084,6 +11169,7 @@
   public class ImsManager {
     method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int);
     method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int);
+    field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
   }
 
   public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
@@ -11383,8 +11469,22 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+    field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19
+    field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13
+    field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12
+    field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14
+    field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11
+    field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17
+    field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16
+    field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15
+    field public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; // 0x10
+    field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf
+    field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7
     field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
     field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
+    field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa
+    field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb
+    field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff
     field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
     field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
     field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
@@ -11397,6 +11497,47 @@
     method public void onProvisioningStringChanged(int, @NonNull String);
   }
 
+  public final class RcsContactUceCapability implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<java.lang.String> getCapableExtensionTags();
+    method @NonNull public android.net.Uri getContactUri();
+    method @Nullable public android.net.Uri getServiceUri(int);
+    method public boolean isCapable(int);
+    method public boolean isCapable(@NonNull String);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CAPABILITY_CHAT_SESSION = 2; // 0x2
+    field public static final int CAPABILITY_CHAT_SESSION_STORE_FORWARD = 4; // 0x4
+    field public static final int CAPABILITY_CHAT_STANDALONE = 1; // 0x1
+    field public static final int CAPABILITY_DISCOVERY_VIA_PRESENCE = 4096; // 0x1000
+    field public static final int CAPABILITY_FILE_TRANSFER = 8; // 0x8
+    field public static final int CAPABILITY_FILE_TRANSFER_HTTP = 64; // 0x40
+    field public static final int CAPABILITY_FILE_TRANSFER_SMS = 128; // 0x80
+    field public static final int CAPABILITY_FILE_TRANSFER_STORE_FORWARD = 32; // 0x20
+    field public static final int CAPABILITY_FILE_TRANSFER_THUMBNAIL = 16; // 0x10
+    field public static final int CAPABILITY_GEOLOCATION_PULL = 131072; // 0x20000
+    field public static final int CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER = 262144; // 0x40000
+    field public static final int CAPABILITY_GEOLOCATION_PUSH = 32768; // 0x8000
+    field public static final int CAPABILITY_GEOLOCATION_PUSH_SMS = 65536; // 0x10000
+    field public static final int CAPABILITY_IMAGE_SHARE = 256; // 0x100
+    field public static final int CAPABILITY_IP_VIDEO_CALL = 16384; // 0x4000
+    field public static final int CAPABILITY_IP_VOICE_CALL = 8192; // 0x2000
+    field public static final int CAPABILITY_RCS_VIDEO_CALL = 1048576; // 0x100000
+    field public static final int CAPABILITY_RCS_VIDEO_ONLY_CALL = 2097152; // 0x200000
+    field public static final int CAPABILITY_RCS_VOICE_CALL = 524288; // 0x80000
+    field public static final int CAPABILITY_SOCIAL_PRESENCE = 2048; // 0x800
+    field public static final int CAPABILITY_VIDEO_SHARE = 1024; // 0x400
+    field public static final int CAPABILITY_VIDEO_SHARE_DURING_CS_CALL = 512; // 0x200
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsContactUceCapability> CREATOR;
+  }
+
+  public static class RcsContactUceCapability.Builder {
+    ctor public RcsContactUceCapability.Builder(@NonNull android.net.Uri);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(int, @NonNull android.net.Uri);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(int);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(@NonNull String);
+    method @NonNull public android.telephony.ims.RcsContactUceCapability build();
+  }
+
   public interface RegistrationManager {
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
diff --git a/api/test-current.txt b/api/test-current.txt
index d017dd6..a8f7b51 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -435,6 +435,8 @@
   }
 
   public class StatusBarManager {
+    method public void collapsePanels();
+    method public void expandNotificationsPanel();
     method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo();
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean);
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean);
@@ -2929,7 +2931,10 @@
     method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel);
     method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
     method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String);
+    method public void onNotificationVisibilityChanged(@NonNull String, boolean);
     method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>);
+    method public void onPanelHidden();
+    method public void onPanelRevealed(int);
     method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
     method public final void unsnoozeNotification(@NonNull String);
     field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
@@ -3517,6 +3522,7 @@
   public class ImsManager {
     method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int);
     method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int);
+    field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
   }
 
   public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
@@ -3812,8 +3818,22 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+    field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19
+    field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13
+    field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12
+    field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14
+    field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11
+    field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17
+    field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16
+    field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15
+    field public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; // 0x10
+    field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf
+    field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7
     field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
     field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
+    field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa
+    field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb
+    field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff
     field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
     field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
     field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 19b9709..2bacfbc 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -333,10 +333,11 @@
         MediaProviderSchemaChange media_provider_schema_change = 236 [(module) = "mediaprovider"];
         MediaProviderIdleMaintenance media_provider_idle_maintenance =
             237 [(module) = "mediaprovider"];
+        RebootEscrowRecoveryReported reboot_escrow_recovery_reported = 238;
     }
 
     // Pulled events will start at field 10000.
-    // Next: 10068
+    // Next: 10069
     oneof pulled {
         WifiBytesTransfer wifi_bytes_transfer = 10000;
         WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -405,6 +406,7 @@
         VmsClientStats vms_client_stats = 10065;
         NotificationRemoteViews notification_remote_views = 10066;
         DangerousPermissionStateSampled dangerous_permission_state_sampled = 10067;
+        GraphicsStats graphics_stats = 10068;
     }
 
     // DO NOT USE field numbers above 100,000 in AOSP.
@@ -7338,6 +7340,17 @@
 }
 
 /**
+ * Reported when the RebootEscrow HAL has attempted to recover the escrowed
+ * key to indicate whether it was successful or not.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+ */
+message RebootEscrowRecoveryReported {
+    optional bool successful = 1;
+}
+
+/**
  * Global display pipeline metrics reported by SurfaceFlinger.
  * Pulled from:
  *    frameworks/native/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -7553,3 +7566,69 @@
     optional int32 permission_flags = 4;
 }
 
+/**
+ * HWUI renders pipeline type: GL (0) or Vulkan (1).
+ */
+enum PipelineType {
+    GL = 0;
+    VULKAN = 1;
+}
+
+/**
+ * HWUI stats for a given app.
+ */
+message GraphicsStats {
+    // The package name of the app
+    optional string package_name = 1;
+
+    // The version code of the app
+    optional int64 version_code = 2;
+
+    // The start & end timestamps in UTC as
+    // milliseconds since January 1, 1970
+    // Compatible with java.util.Date#setTime()
+    optional int64 stats_start = 3;
+
+    optional int64 stats_end = 4;
+
+    // HWUI renders pipeline type: GL or Vulkan.
+    optional PipelineType pipeline = 5;
+
+    // Distinct frame count.
+    optional int32 total_frames = 6;
+
+    // Number of "missed vsync" events.
+    optional int32 missed_vsync_count = 7;
+
+    // Number of frames in triple-buffering scenario (high input latency)
+    optional int32 high_input_latency_count = 8;
+
+    // Number of "slow UI thread" events.
+    optional int32 slow_ui_thread_count = 9;
+
+    // Number of "slow bitmap upload" events.
+    optional int32 slow_bitmap_upload_count = 10;
+
+    // Number of "slow draw" events.
+    optional int32 slow_draw_count = 11;
+
+    // Number of frames that missed their deadline (aka, visibly janked)
+    optional int32 missed_deadline_count = 12;
+
+    // The frame time histogram for the package
+    optional FrameTimingHistogram cpu_histogram = 13
+    [(android.os.statsd.log_mode) = MODE_BYTES];
+
+    // The gpu frame time histogram for the package
+    optional FrameTimingHistogram gpu_histogram = 14
+    [(android.os.statsd.log_mode) = MODE_BYTES];
+
+    // UI mainline module version.
+    optional int64 version_ui_module = 15;
+
+    // If true, these are HWUI stats for up to a 24h period for a given app from today.
+    // If false, these are HWUI stats for a 24h period for a given app from the last complete
+    // day (yesterday). Stats from yesterday stay constant, while stats from today may change as
+    // more apps are running / rendering.
+    optional bool is_today = 16;
+}
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 1d31873..b568033 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -59,161 +59,200 @@
 const int64_t NO_ALARM_UPDATE = INT64_MAX;
 
 std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
-        // wifi_bytes_transfer
-        {{.atomTag = android::util::WIFI_BYTES_TRANSFER},
-         {.additiveFields = {2, 3, 4, 5},
-          .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER)}},
+
         // wifi_bytes_transfer_by_fg_bg
         {{.atomTag = android::util::WIFI_BYTES_TRANSFER_BY_FG_BG},
          {.additiveFields = {3, 4, 5, 6},
           .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER_BY_FG_BG)}},
+
         // mobile_bytes_transfer
         {{.atomTag = android::util::MOBILE_BYTES_TRANSFER},
          {.additiveFields = {2, 3, 4, 5},
           .puller = new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER)}},
+
         // mobile_bytes_transfer_by_fg_bg
         {{.atomTag = android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG},
          {.additiveFields = {3, 4, 5, 6},
           .puller =
                   new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)}},
+
         // bluetooth_bytes_transfer
         {{.atomTag = android::util::BLUETOOTH_BYTES_TRANSFER},
          {.additiveFields = {2, 3},
           .puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_BYTES_TRANSFER)}},
+
         // kernel_wakelock
         {{.atomTag = android::util::KERNEL_WAKELOCK},
          {.puller = new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}},
+
         // subsystem_sleep_state
         {{.atomTag = android::util::SUBSYSTEM_SLEEP_STATE},
          {.puller = new SubsystemSleepStatePuller()}},
+
         // on_device_power_measurement
         {{.atomTag = android::util::ON_DEVICE_POWER_MEASUREMENT},
          {.puller = new PowerStatsPuller()}},
+
         // cpu_time_per_freq
         {{.atomTag = android::util::CPU_TIME_PER_FREQ},
          {.additiveFields = {3},
           .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_FREQ)}},
+
         // cpu_time_per_uid
         {{.atomTag = android::util::CPU_TIME_PER_UID},
          {.additiveFields = {2, 3},
           .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID)}},
+
         // cpu_time_per_uid_freq
         // the throttling is 3sec, handled in
         // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
         {{.atomTag = android::util::CPU_TIME_PER_UID_FREQ},
          {.additiveFields = {4},
           .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID_FREQ)}},
+
         // cpu_active_time
         // the throttling is 3sec, handled in
         // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
         {{.atomTag = android::util::CPU_ACTIVE_TIME},
          {.additiveFields = {2},
           .puller = new StatsCompanionServicePuller(android::util::CPU_ACTIVE_TIME)}},
+
         // cpu_cluster_time
         // the throttling is 3sec, handled in
         // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
         {{.atomTag = android::util::CPU_CLUSTER_TIME},
          {.additiveFields = {3},
           .puller = new StatsCompanionServicePuller(android::util::CPU_CLUSTER_TIME)}},
+
         // wifi_activity_energy_info
         {{.atomTag = android::util::WIFI_ACTIVITY_INFO},
          {.puller = new StatsCompanionServicePuller(android::util::WIFI_ACTIVITY_INFO)}},
+
         // modem_activity_info
         {{.atomTag = android::util::MODEM_ACTIVITY_INFO},
          {.puller = new StatsCompanionServicePuller(android::util::MODEM_ACTIVITY_INFO)}},
+
         // bluetooth_activity_info
         {{.atomTag = android::util::BLUETOOTH_ACTIVITY_INFO},
          {.puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}},
+
         // system_elapsed_realtime
         {{.atomTag = android::util::SYSTEM_ELAPSED_REALTIME},
          {.coolDownNs = NS_PER_SEC,
           .puller = new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME),
           .pullTimeoutNs = NS_PER_SEC / 2,
          }},
+
         // system_uptime
         {{.atomTag = android::util::SYSTEM_UPTIME},
          {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}},
+
         // remaining_battery_capacity
         {{.atomTag = android::util::REMAINING_BATTERY_CAPACITY},
          {.puller = new ResourceHealthManagerPuller(android::util::REMAINING_BATTERY_CAPACITY)}},
+
         // full_battery_capacity
         {{.atomTag = android::util::FULL_BATTERY_CAPACITY},
          {.puller = new ResourceHealthManagerPuller(android::util::FULL_BATTERY_CAPACITY)}},
+
         // battery_voltage
         {{.atomTag = android::util::BATTERY_VOLTAGE},
          {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}},
+
         // battery_level
         {{.atomTag = android::util::BATTERY_LEVEL},
          {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_LEVEL)}},
+
         // battery_cycle_count
         {{.atomTag = android::util::BATTERY_CYCLE_COUNT},
          {.puller = new ResourceHealthManagerPuller(android::util::BATTERY_CYCLE_COUNT)}},
+
         // process_memory_state
         {{.atomTag = android::util::PROCESS_MEMORY_STATE},
          {.additiveFields = {4, 5, 6, 7, 8},
           .puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_STATE)}},
+
         // process_memory_high_water_mark
         {{.atomTag = android::util::PROCESS_MEMORY_HIGH_WATER_MARK},
          {.puller =
                   new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}},
+
         // process_memory_snapshot
         {{.atomTag = android::util::PROCESS_MEMORY_SNAPSHOT},
          {.puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_SNAPSHOT)}},
+
         // system_ion_heap_size
         {{.atomTag = android::util::SYSTEM_ION_HEAP_SIZE},
          {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_ION_HEAP_SIZE)}},
+
         // process_system_ion_heap_size
         {{.atomTag = android::util::PROCESS_SYSTEM_ION_HEAP_SIZE},
          {.puller = new StatsCompanionServicePuller(android::util::PROCESS_SYSTEM_ION_HEAP_SIZE)}},
+
         // temperature
         {{.atomTag = android::util::TEMPERATURE},
          {.puller = new StatsCompanionServicePuller(android::util::TEMPERATURE)}},
+
         // cooling_device
         {{.atomTag = android::util::COOLING_DEVICE},
          {.puller = new StatsCompanionServicePuller(android::util::COOLING_DEVICE)}},
+
         // binder_calls
         {{.atomTag = android::util::BINDER_CALLS},
          {.additiveFields = {4, 5, 6, 8, 12},
           .puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS)}},
+
         // binder_calls_exceptions
         {{.atomTag = android::util::BINDER_CALLS_EXCEPTIONS},
          {.puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS_EXCEPTIONS)}},
+
         // looper_stats
         {{.atomTag = android::util::LOOPER_STATS},
          {.additiveFields = {5, 6, 7, 8, 9},
           .puller = new StatsCompanionServicePuller(android::util::LOOPER_STATS)}},
+
         // Disk Stats
         {{.atomTag = android::util::DISK_STATS},
          {.puller = new StatsCompanionServicePuller(android::util::DISK_STATS)}},
+
         // Directory usage
         {{.atomTag = android::util::DIRECTORY_USAGE},
          {.puller = new StatsCompanionServicePuller(android::util::DIRECTORY_USAGE)}},
+
         // Size of app's code, data, and cache
         {{.atomTag = android::util::APP_SIZE},
          {.puller = new StatsCompanionServicePuller(android::util::APP_SIZE)}},
+
         // Size of specific categories of files. Eg. Music.
         {{.atomTag = android::util::CATEGORY_SIZE},
          {.puller = new StatsCompanionServicePuller(android::util::CATEGORY_SIZE)}},
+
         // Number of fingerprints enrolled for each user.
         {{.atomTag = android::util::NUM_FINGERPRINTS_ENROLLED},
          {.puller = new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS_ENROLLED)}},
+
         // Number of faces enrolled for each user.
         {{.atomTag = android::util::NUM_FACES_ENROLLED},
          {.puller = new StatsCompanionServicePuller(android::util::NUM_FACES_ENROLLED)}},
+
         // ProcStats.
         {{.atomTag = android::util::PROC_STATS},
          {.puller = new StatsCompanionServicePuller(android::util::PROC_STATS)}},
+
         // ProcStatsPkgProc.
         {{.atomTag = android::util::PROC_STATS_PKG_PROC},
          {.puller = new StatsCompanionServicePuller(android::util::PROC_STATS_PKG_PROC)}},
+
         // Disk I/O stats per uid.
         {{.atomTag = android::util::DISK_IO},
          {.additiveFields = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
           .coolDownNs = 3 * NS_PER_SEC,
           .puller = new StatsCompanionServicePuller(android::util::DISK_IO)}},
+
         // PowerProfile constants for power model calculations.
         {{.atomTag = android::util::POWER_PROFILE},
          {.puller = new StatsCompanionServicePuller(android::util::POWER_PROFILE)}},
+
         // Process cpu stats. Min cool-down is 5 sec, inline with what AcitivityManagerService uses.
         {{.atomTag = android::util::PROCESS_CPU_TIME},
          {.coolDownNs = 5 * NS_PER_SEC /* min cool-down in seconds*/,
@@ -221,64 +260,83 @@
         {{.atomTag = android::util::CPU_TIME_PER_THREAD_FREQ},
          {.additiveFields = {7, 9, 11, 13, 15, 17, 19, 21},
           .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_THREAD_FREQ)}},
+
         // DeviceCalculatedPowerUse.
         {{.atomTag = android::util::DEVICE_CALCULATED_POWER_USE},
          {.puller = new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_USE)}},
+
         // DeviceCalculatedPowerBlameUid.
         {{.atomTag = android::util::DEVICE_CALCULATED_POWER_BLAME_UID},
          {.puller = new StatsCompanionServicePuller(
                   android::util::DEVICE_CALCULATED_POWER_BLAME_UID)}},
+
         // DeviceCalculatedPowerBlameOther.
         {{.atomTag = android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER},
          {.puller = new StatsCompanionServicePuller(
                   android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}},
+
         // DebugElapsedClock.
         {{.atomTag = android::util::DEBUG_ELAPSED_CLOCK},
          {.additiveFields = {1, 2, 3, 4},
           .puller = new StatsCompanionServicePuller(android::util::DEBUG_ELAPSED_CLOCK)}},
+
         // DebugFailingElapsedClock.
         {{.atomTag = android::util::DEBUG_FAILING_ELAPSED_CLOCK},
          {.additiveFields = {1, 2, 3, 4},
           .puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}},
+
         // BuildInformation.
         {{.atomTag = android::util::BUILD_INFORMATION},
          {.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}},
+
         // RoleHolder.
         {{.atomTag = android::util::ROLE_HOLDER},
          {.puller = new StatsCompanionServicePuller(android::util::ROLE_HOLDER)}},
+
         // PermissionState.
         {{.atomTag = android::util::DANGEROUS_PERMISSION_STATE},
          {.puller = new StatsCompanionServicePuller(android::util::DANGEROUS_PERMISSION_STATE)}},
+
         // TrainInfo.
         {{.atomTag = android::util::TRAIN_INFO}, {.puller = new TrainInfoPuller()}},
+
         // TimeZoneDataInfo.
         {{.atomTag = android::util::TIME_ZONE_DATA_INFO},
          {.puller = new StatsCompanionServicePuller(android::util::TIME_ZONE_DATA_INFO)}},
+
         // ExternalStorageInfo
         {{.atomTag = android::util::EXTERNAL_STORAGE_INFO},
          {.puller = new StatsCompanionServicePuller(android::util::EXTERNAL_STORAGE_INFO)}},
+
         // GpuStatsGlobalInfo
         {{.atomTag = android::util::GPU_STATS_GLOBAL_INFO},
          {.puller = new GpuStatsPuller(android::util::GPU_STATS_GLOBAL_INFO)}},
+
         // GpuStatsAppInfo
         {{.atomTag = android::util::GPU_STATS_APP_INFO},
          {.puller = new GpuStatsPuller(android::util::GPU_STATS_APP_INFO)}},
+
         // AppsOnExternalStorageInfo
         {{.atomTag = android::util::APPS_ON_EXTERNAL_STORAGE_INFO},
          {.puller = new StatsCompanionServicePuller(android::util::APPS_ON_EXTERNAL_STORAGE_INFO)}},
+
         // Face Settings
         {{.atomTag = android::util::FACE_SETTINGS},
          {.puller = new StatsCompanionServicePuller(android::util::FACE_SETTINGS)}},
+
         // App ops
         {{.atomTag = android::util::APP_OPS},
          {.puller = new StatsCompanionServicePuller(android::util::APP_OPS)}},
+
         // VmsClientStats
         {{.atomTag = android::util::VMS_CLIENT_STATS},
          {.additiveFields = {5, 6, 7, 8, 9, 10},
           .puller = new CarStatsPuller(android::util::VMS_CLIENT_STATS)}},
+
         // NotiifcationRemoteViews.
         {{.atomTag = android::util::NOTIFICATION_REMOTE_VIEWS},
          {.puller = new StatsCompanionServicePuller(android::util::NOTIFICATION_REMOTE_VIEWS)}},
+
         // PermissionStateSampled.
         {{.atomTag = android::util::DANGEROUS_PERMISSION_STATE_SAMPLED},
          {.puller =
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 4d0acb3..4aaf727 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -22519,8 +22519,6 @@
 HSPLcom/android/internal/telephony/PhoneFactory;->makeDefaultPhones(Landroid/content/Context;)V
 HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;-><init>(Ljava/lang/String;I)V
 HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;->values()[Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;
-HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;-><init>(Landroid/content/Context;Landroid/os/Handler;)V
-HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;->notifyServiceState(I)V
 HSPLcom/android/internal/telephony/PhoneSubInfoController;->callPhoneMethodWithPermissionCheck(ILjava/lang/String;Ljava/lang/String;Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper;Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper;)Ljava/lang/Object;
 HSPLcom/android/internal/telephony/PhoneSubInfoController;->getCarrierInfoForImsiEncryption(IILjava/lang/String;)Landroid/telephony/ImsiEncryptionInfo;
 HSPLcom/android/internal/telephony/PhoneSubInfoController;->getGroupIdLevel1ForSubscriber(ILjava/lang/String;)Ljava/lang/String;
@@ -37729,7 +37727,6 @@
 Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;
 Lcom/android/internal/telephony/PhoneInternalInterface;
 Lcom/android/internal/telephony/PhoneNotifier;
-Lcom/android/internal/telephony/PhoneStateIntentReceiver;
 Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper;
 Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper;
 Lcom/android/internal/telephony/PhoneSubInfoController;
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 97f009c..e53c74b 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -4841,7 +4841,6 @@
 com.android.internal.telephony.PhoneFactory
 com.android.internal.telephony.PhoneInternalInterface
 com.android.internal.telephony.PhoneNotifier
-com.android.internal.telephony.PhoneStateIntentReceiver
 com.android.internal.telephony.PhoneSubInfoController$CallPhoneMethodHelper
 com.android.internal.telephony.PhoneSubInfoController$PermissionCheckHelper
 com.android.internal.telephony.PhoneSubInfoController
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index c0fee6e..e46840c 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -1547,6 +1547,34 @@
             void onShowModeChanged(@NonNull SoftKeyboardController controller,
                     @SoftKeyboardShowMode int showMode);
         }
+
+        /**
+         * Switches the current IME for the user for whom the service is enabled. The change will
+         * persist until the current IME is explicitly changed again, and may persist beyond the
+         * life cycle of the requesting service.
+         *
+         * @param imeId The ID of the input method to make current. This IME must be installed and
+         *              enabled.
+         * @return {@code true} if the current input method was successfully switched to the input
+         *         method by {@code imeId},
+         *         {@code false} if the input method specified is not installed, not enabled, or
+         *         otherwise not available to become the current IME
+         *
+         * @see android.view.inputmethod.InputMethodInfo#getId()
+         */
+        public boolean switchToInputMethod(@NonNull String imeId) {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.switchToInputMethod(imeId);
+                } catch (RemoteException re) {
+                    throw new RuntimeException(re);
+                }
+            }
+            return false;
+        }
     }
 
     /**
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 4841781..656f87f 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -91,6 +91,8 @@
 
     void setSoftKeyboardCallbackEnabled(boolean enabled);
 
+    boolean switchToInputMethod(String imeId);
+
     boolean isAccessibilityButtonAvailable();
 
     void sendGesture(int sequence, in ParceledListSlice gestureSteps);
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 032e824..4f3e8ec 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -45,8 +45,19 @@
 
     // Access modes for handleIncomingUser.
     public static final int ALLOW_NON_FULL = 0;
+    /**
+     * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS}
+     * if in the same profile group.
+     * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
+     */
     public static final int ALLOW_NON_FULL_IN_PROFILE = 1;
     public static final int ALLOW_FULL_ONLY = 2;
+    /**
+     * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
+     * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} if in the same profile group.
+     * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
+     */
+    public static final int ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE = 3;
 
     /**
      * Verify that calling app has access to the given provider.
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 4a8e4e2..a11f41f 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -726,7 +726,17 @@
     /** @hide Capture the device's display contents and/or audio */
     @UnsupportedAppUsage
     public static final int OP_PROJECT_MEDIA = 46;
-    /** @hide Activate a VPN connection without user intervention. */
+    /**
+     * Start (without additional user intervention) a VPN connection, as used by {@link
+     * android.net.VpnService} along with as Platform VPN connections, as used by {@link
+     * android.net.VpnManager}
+     *
+     * <p>This appop is granted to apps that have already been given user consent to start
+     * VpnService based VPN connections. As this is a superset of OP_ACTIVATE_PLATFORM_VPN, this
+     * appop also allows the starting of Platform VPNs.
+     *
+     * @hide
+     */
     @UnsupportedAppUsage
     public static final int OP_ACTIVATE_VPN = 47;
     /** @hide Access the WallpaperManagerAPI to write wallpapers. */
@@ -852,10 +862,21 @@
     public static final int OP_MANAGE_EXTERNAL_STORAGE = 92;
     /** @hide Communicate cross-profile within the same profile group. */
     public static final int OP_INTERACT_ACROSS_PROFILES = 93;
+    /**
+     * Start (without additional user intervention) a Platform VPN connection, as used by {@link
+     * android.net.VpnManager}
+     *
+     * <p>This appop is granted to apps that have already been given user consent to start Platform
+     * VPN connections. This appop is insufficient to start VpnService based VPNs; OP_ACTIVATE_VPN
+     * is needed for that.
+     *
+     * @hide
+     */
+    public static final int OP_ACTIVATE_PLATFORM_VPN = 94;
 
     /** @hide */
     @UnsupportedAppUsage
-    public static final int _NUM_OP = 94;
+    public static final int _NUM_OP = 95;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1149,6 +1170,8 @@
     /** @hide Communicate cross-profile within the same profile group. */
     @SystemApi
     public static final String OPSTR_INTERACT_ACROSS_PROFILES = "android:interact_across_profiles";
+    /** @hide Start Platform VPN without user intervention */
+    public static final String OPSTR_ACTIVATE_PLATFORM_VPN = "android:activate_platform_vpn";
 
 
     /** {@link #sAppOpsToNote} not initialized yet for this op */
@@ -1333,6 +1356,7 @@
             OP_QUERY_ALL_PACKAGES,              // QUERY_ALL_PACKAGES
             OP_MANAGE_EXTERNAL_STORAGE,         // MANAGE_EXTERNAL_STORAGE
             OP_INTERACT_ACROSS_PROFILES,        //INTERACT_ACROSS_PROFILES
+            OP_ACTIVATE_PLATFORM_VPN,           // ACTIVATE_PLATFORM_VPN
     };
 
     /**
@@ -1433,6 +1457,7 @@
             OPSTR_QUERY_ALL_PACKAGES,
             OPSTR_MANAGE_EXTERNAL_STORAGE,
             OPSTR_INTERACT_ACROSS_PROFILES,
+            OPSTR_ACTIVATE_PLATFORM_VPN,
     };
 
     /**
@@ -1533,7 +1558,8 @@
             "ACCESS_MEDIA_LOCATION",
             "QUERY_ALL_PACKAGES",
             "MANAGE_EXTERNAL_STORAGE",
-            "INTERACT_ACROSS_PROFILES"
+            "INTERACT_ACROSS_PROFILES",
+            "ACTIVATE_PLATFORM_VPN",
     };
 
     /**
@@ -1636,6 +1662,7 @@
             null, // no permission for OP_QUERY_ALL_PACKAGES
             Manifest.permission.MANAGE_EXTERNAL_STORAGE,
             android.Manifest.permission.INTERACT_ACROSS_PROFILES,
+            null, // no permission for OP_ACTIVATE_PLATFORM_VPN
     };
 
     /**
@@ -1738,6 +1765,7 @@
             null, // QUERY_ALL_PACKAGES
             null, // MANAGE_EXTERNAL_STORAGE
             null, // INTERACT_ACROSS_PROFILES
+            null, // ACTIVATE_PLATFORM_VPN
     };
 
     /**
@@ -1839,6 +1867,7 @@
             false, // QUERY_ALL_PACKAGES
             false, // MANAGE_EXTERNAL_STORAGE
             false, // INTERACT_ACROSS_PROFILES
+            false, // ACTIVATE_PLATFORM_VPN
     };
 
     /**
@@ -1939,6 +1968,7 @@
             AppOpsManager.MODE_DEFAULT, // QUERY_ALL_PACKAGES
             AppOpsManager.MODE_DEFAULT, // MANAGE_EXTERNAL_STORAGE
             AppOpsManager.MODE_DEFAULT, // INTERACT_ACROSS_PROFILES
+            AppOpsManager.MODE_IGNORED, // ACTIVATE_PLATFORM_VPN
     };
 
     /**
@@ -2043,6 +2073,7 @@
             false, // QUERY_ALL_PACKAGES
             false, // MANAGE_EXTERNAL_STORAGE
             false, // INTERACT_ACROSS_PROFILES
+            false, // ACTIVATE_PLATFORM_VPN
     };
 
     /**
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
index d993ec1..3febf71 100644
--- a/core/java/android/app/AsyncNotedAppOp.java
+++ b/core/java/android/app/AsyncNotedAppOp.java
@@ -256,10 +256,10 @@
     };
 
     @DataClass.Generated(
-            time = 1578321462996L,
+            time = 1578516519372L,
             codegenVersion = "1.0.14",
             sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
-            inputSignatures = "private final @android.annotation.IntRange(from=0L, to=93L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
+            inputSignatures = "private final @android.annotation.IntRange(from=0L, to=94L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 700b3c1..e5c046c 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -68,6 +68,7 @@
 import android.os.WorkSource;
 import android.service.voice.IVoiceInteractionSession;
 import android.view.IRecentsAnimationRunner;
+import android.view.ITaskOrganizer;
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationAdapter;
 import android.view.WindowContainerTransaction;
@@ -121,6 +122,9 @@
             in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho,
             int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options,
             IBinder permissionToken, boolean ignoreTargetSecurity, int userId);
+
+    void registerTaskOrganizer(in ITaskOrganizer organizer, int windowingMode);
+
     boolean isActivityStartAllowedOnDisplay(int displayId, in Intent intent, in String resolvedType,
             int userId);
 
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 86f52af..fcdb7cc 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -94,8 +94,11 @@
     void updateNotificationChannelGroupForPackage(String pkg, int uid, in NotificationChannelGroup group);
     void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel);
     NotificationChannel getNotificationChannel(String callingPkg, int userId, String pkg, String channelId);
+    NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, String conversationId);
+    void createConversationNotificationChannelForPackage(String pkg, int uid, in NotificationChannel parentChannel, String conversationId);
     NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted);
     void deleteNotificationChannel(String pkg, String channelId);
+    void deleteConversationNotificationChannels(String pkg, int uid, String conversationId);
     ParceledListSlice getNotificationChannels(String callingPkg, String targetPkg, int userId);
     ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
     int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 3eee1ae..a33c2c1 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -23,6 +23,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ShortcutInfo;
 import android.media.AudioAttributes;
 import android.net.Uri;
 import android.os.Parcel;
@@ -57,6 +58,22 @@
     public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
 
     /**
+     * The formatter used by the system to create an id for notification
+     * channels when it automatically creates conversation channels on behalf of an app. The format
+     * string takes two arguments, in this order: the
+     * {@link #getId()} of the original notification channel, and the
+     * {@link ShortcutInfo#getId() id} of the conversation.
+     */
+    public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s";
+
+    /**
+     * TODO: STOPSHIP  remove
+     * Conversation id to use for apps that aren't providing them yet.
+     * @hide
+     */
+    public static final String PLACEHOLDER_CONVERSATION_ID = "placeholder_id";
+
+    /**
      * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
      * limit.
      */
@@ -85,6 +102,8 @@
     private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system";
     private static final String ATT_ALLOW_BUBBLE = "can_bubble";
     private static final String ATT_ORIG_IMP = "orig_imp";
+    private static final String ATT_PARENT_CHANNEL = "parent";
+    private static final String ATT_CONVERSATION_ID = "conv_id";
     private static final String DELIMITER = ",";
 
     /**
@@ -147,7 +166,7 @@
     private static final boolean DEFAULT_ALLOW_BUBBLE = true;
 
     @UnsupportedAppUsage
-    private final String mId;
+    private String mId;
     private String mName;
     private String mDesc;
     private int mImportance = DEFAULT_IMPORTANCE;
@@ -172,6 +191,8 @@
     private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE;
     private boolean mImportanceLockedByOEM;
     private boolean mImportanceLockedDefaultApp;
+    private String mParentId = null;
+    private String mConversationId = null;
 
     /**
      * Creates a notification channel.
@@ -236,6 +257,8 @@
         mAllowBubbles = in.readBoolean();
         mImportanceLockedByOEM = in.readBoolean();
         mOriginalImportance = in.readInt();
+        mParentId = in.readString();
+        mConversationId = in.readString();
     }
 
     @Override
@@ -291,6 +314,8 @@
         dest.writeBoolean(mAllowBubbles);
         dest.writeBoolean(mImportanceLockedByOEM);
         dest.writeInt(mOriginalImportance);
+        dest.writeString(mParentId);
+        dest.writeString(mConversationId);
     }
 
     /**
@@ -363,6 +388,13 @@
     // Modifiable by apps on channel creation.
 
     /**
+     * @hide
+     */
+    public void setId(String id) {
+        mId = id;
+    }
+
+    /**
      * Sets what group this channel belongs to.
      *
      * Group information is only used for presentation, not for behavior.
@@ -502,6 +534,23 @@
     }
 
     /**
+     * Sets this channel as being person-centric. Different settings and functionality may be
+     * exposed for people-centric channels.
+     *
+     * @param parentChannelId The {@link #getId()} id} of the generic channel that notifications of
+     *                        this type would be posted to in absence of a specific conversation id.
+     *                        For example, if this channel represents 'Messages from Person A', the
+     *                        parent channel would be 'Messages.'
+     * @param conversationId The {@link ShortcutInfo#getId()} of the shortcut representing this
+     *                       channel's conversation.
+     */
+    public void setConversationId(@Nullable String parentChannelId,
+            @Nullable String conversationId) {
+        mParentId = parentChannelId;
+        mConversationId = conversationId;
+    }
+
+    /**
      * Returns the id of this channel.
      */
     public String getId() {
@@ -622,6 +671,22 @@
     }
 
     /**
+     * Returns the {@link #getId() id} of the parent notification channel to this channel, if it's
+     * a conversation related channel. See {@link #setConversationId(String, String)}.
+     */
+    public @Nullable String getParentChannelId() {
+        return mParentId;
+    }
+
+    /**
+     * Returns the {@link ShortcutInfo#getId() id} of the conversation backing this channel, if it's
+     * associated with a conversation. See {@link #setConversationId(String, String)}.
+     */
+    public @Nullable String getConversationId() {
+        return mConversationId;
+    }
+
+    /**
      * @hide
      */
     @SystemApi
@@ -761,6 +826,8 @@
         setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
         setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
         setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE));
+        setConversationId(parser.getAttributeValue(ATT_PARENT_CHANNEL, null),
+                parser.getAttributeValue(ATT_CONVERSATION_ID, null));
     }
 
     @Nullable
@@ -885,6 +952,12 @@
         if (getOriginalImportance() != DEFAULT_IMPORTANCE) {
             out.attribute(null, ATT_ORIG_IMP, Integer.toString(getOriginalImportance()));
         }
+        if (getParentChannelId() != null) {
+            out.attribute(null, ATT_PARENT_CHANNEL, getParentChannelId());
+        }
+        if (getConversationId() != null) {
+            out.attribute(null, ATT_CONVERSATION_ID, getConversationId());
+        }
 
         // mImportanceLockedDefaultApp and mImportanceLockedByOEM have a different source of
         // truth and so aren't written to this xml file
@@ -1042,7 +1115,9 @@
                 && Objects.equals(getAudioAttributes(), that.getAudioAttributes())
                 && mImportanceLockedByOEM == that.mImportanceLockedByOEM
                 && mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp
-                && mOriginalImportance == that.mOriginalImportance;
+                && mOriginalImportance == that.mOriginalImportance
+                && Objects.equals(getParentChannelId(), that.getParentChannelId())
+                && Objects.equals(getConversationId(), that.getConversationId());
     }
 
     @Override
@@ -1052,7 +1127,8 @@
                 getUserLockedFields(),
                 isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(),
                 getAudioAttributes(), isBlockableSystem(), mAllowBubbles,
-                mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance);
+                mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance,
+                mParentId, mConversationId);
         result = 31 * result + Arrays.hashCode(mVibration);
         return result;
     }
@@ -1063,26 +1139,7 @@
         String output = "NotificationChannel{"
                 + "mId='" + mId + '\''
                 + ", mName=" + redactedName
-                + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
-                + ", mImportance=" + mImportance
-                + ", mBypassDnd=" + mBypassDnd
-                + ", mLockscreenVisibility=" + mLockscreenVisibility
-                + ", mSound=" + mSound
-                + ", mLights=" + mLights
-                + ", mLightColor=" + mLightColor
-                + ", mVibration=" + Arrays.toString(mVibration)
-                + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields)
-                + ", mFgServiceShown=" + mFgServiceShown
-                + ", mVibrationEnabled=" + mVibrationEnabled
-                + ", mShowBadge=" + mShowBadge
-                + ", mDeleted=" + mDeleted
-                + ", mGroup='" + mGroup + '\''
-                + ", mAudioAttributes=" + mAudioAttributes
-                + ", mBlockableSystem=" + mBlockableSystem
-                + ", mAllowBubbles=" + mAllowBubbles
-                + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
-                + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
-                + ", mOriginalImp=" + mOriginalImportance
+                + getFieldsString()
                 + '}';
         pw.println(prefix + output);
     }
@@ -1092,7 +1149,12 @@
         return "NotificationChannel{"
                 + "mId='" + mId + '\''
                 + ", mName=" + mName
-                + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
+                + getFieldsString()
+                + '}';
+    }
+
+    private String getFieldsString() {
+        return  ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
                 + ", mImportance=" + mImportance
                 + ", mBypassDnd=" + mBypassDnd
                 + ", mLockscreenVisibility=" + mLockscreenVisibility
@@ -1112,7 +1174,8 @@
                 + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
                 + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
                 + ", mOriginalImp=" + mOriginalImportance
-                + '}';
+                + ", mParent=" + mParentId
+                + ", mConversationId" + mConversationId;
     }
 
     /** @hide */
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index fdbb8bb..61c1098 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -843,6 +843,25 @@
     }
 
     /**
+     * Returns the notification channel settings for a given channel and conversation id.
+     *
+     * <p>The channel must belong to your package, or to a package you are an approved notification
+     * delegate for (see {@link #canNotifyAsPackage(String)}), or it will not be returned. To query
+     * a channel as a notification delegate, call this method from a context created for that
+     * package (see {@link Context#createPackageContext(String, int)}).</p>
+     */
+    public @Nullable NotificationChannel getNotificationChannel(@NonNull String channelId,
+            @NonNull String conversationId) {
+        INotificationManager service = getService();
+        try {
+            return service.getConversationNotificationChannel(mContext.getOpPackageName(),
+                    mContext.getUserId(), mContext.getPackageName(), channelId, conversationId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns all notification channels belonging to the calling package.
      *
      * <p>Approved notification delegates (see {@link #canNotifyAsPackage(String)}) can query
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 844e72e..736efb6 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -15,6 +15,7 @@
  */
 
 package android.app;
+
 import android.annotation.NonNull;
 import android.os.SystemProperties;
 import android.util.Log;
@@ -23,6 +24,7 @@
 
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Random;
 import java.util.concurrent.atomic.AtomicLong;
 
@@ -164,6 +166,7 @@
     private static final String TAG = "PropertyInvalidatedCache";
     private static final boolean DEBUG = false;
     private static final boolean ENABLE = true;
+    private static final boolean VERIFY = false;
 
     private final Object mLock = new Object();
 
@@ -228,6 +231,18 @@
     protected abstract Result recompute(Query query);
 
     /**
+     * Determines if a pair of responses are considered equal. Used to determine whether
+     * a cache is inadvertently returning stale results when VERIFY is set to true.
+     */
+    protected boolean debugCompareQueryResults(Result cachedResult, Result fetchedResult) {
+        // If a service crashes and returns a null result, the cached value remains valid.
+        if (fetchedResult != null) {
+            return Objects.equals(cachedResult, fetchedResult);
+        }
+        return true;
+    }
+
+    /**
      * Make result up-to-date on a cache hit.  Called unlocked;
      * may block.
      *
@@ -334,12 +349,12 @@
                             mCache.put(query, refreshedResult);
                         }
                     }
-                    return refreshedResult;
+                    return maybeCheckConsistency(query, refreshedResult);
                 }
                 if (DEBUG) {
                     Log.d(TAG, "cache hit for " + query);
                 }
-                return cachedResult;
+                return maybeCheckConsistency(query, cachedResult);
             }
             // Cache miss: make the value from scratch.
             if (DEBUG) {
@@ -353,7 +368,7 @@
                     mCache.put(query, result);
                 }
             }
-            return result;
+            return maybeCheckConsistency(query, result);
         }
     }
 
@@ -425,4 +440,15 @@
         }
         SystemProperties.set(name, newValueString);
     }
+
+    private Result maybeCheckConsistency(Query query, Result proposedResult) {
+        if (VERIFY) {
+            Result resultToCompare = recompute(query);
+            boolean nonceChanged = (getCurrentNonce() != mLastSeenNonce);
+            if (!nonceChanged && !debugCompareQueryResults(proposedResult, resultToCompare)) {
+                throw new AssertionError("cache returned out of date response for " + query);
+            }
+        }
+        return proposedResult;
+    }
 }
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
index 8426374..dde6dda 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -26,7 +26,6 @@
 import android.os.IBinder;
 import android.os.IPullAtomCallback;
 import android.os.IPullAtomResultReceiver;
-import android.os.IStatsCompanionService;
 import android.os.IStatsManagerService;
 import android.os.IStatsPullerCallback;
 import android.os.IStatsd;
@@ -61,9 +60,6 @@
     private IStatsd mService;
 
     @GuardedBy("sLock")
-    private IStatsCompanionService mStatsCompanion;
-
-    @GuardedBy("sLock")
     private IStatsManagerService mStatsManagerService;
 
     /**
@@ -538,7 +534,7 @@
         }
         synchronized (sLock) {
             try {
-                IStatsCompanionService service = getIStatsCompanionServiceLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 PullAtomCallbackInternal rec =
                     new PullAtomCallbackInternal(atomTag, callback, executor);
                 service.registerPullAtomCallback(atomTag, coolDownNs, timeoutNs, additiveFields,
@@ -560,7 +556,7 @@
     public void unregisterPullAtomCallback(int atomTag) {
         synchronized (sLock) {
             try {
-                IStatsCompanionService service = getIStatsCompanionServiceLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 service.unregisterPullAtomCallback(atomTag);
             } catch (RemoteException e) {
                 throw new RuntimeException("Unable to unregister pull atom callback");
@@ -746,16 +742,6 @@
     }
 
     @GuardedBy("sLock")
-    private IStatsCompanionService getIStatsCompanionServiceLocked() {
-        if (mStatsCompanion != null) {
-            return mStatsCompanion;
-        }
-        mStatsCompanion = IStatsCompanionService.Stub.asInterface(
-                ServiceManager.getService("statscompanion"));
-        return mStatsCompanion;
-    }
-
-    @GuardedBy("sLock")
     private IStatsManagerService getIStatsManagerServiceLocked() {
         if (mStatsManagerService != null) {
             return mStatsManagerService;
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 078e453..42563b5 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -267,6 +267,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @TestApi
     public void expandNotificationsPanel() {
         try {
             final IStatusBarService svc = getService();
@@ -284,6 +285,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @TestApi
     public void collapsePanels() {
         try {
             final IStatusBarService svc = getService();
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 2aac94c..62b499c 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
      */
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index e9b0be2..a923be6 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -16,8 +16,12 @@
 
 package android.bluetooth;
 
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.Binder;
 import android.os.IBinder;
@@ -36,6 +40,7 @@
  */
 public final class BluetoothHidDevice implements BluetoothProfile {
     private static final String TAG = BluetoothHidDevice.class.getSimpleName();
+    private static final boolean DBG = false;
 
     /**
      * Intent used to broadcast the change in connection state of the Input Host profile.
@@ -682,4 +687,62 @@
 
         return result;
     }
+
+    /**
+     * Connects Hid Device if connectionPolicy is {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}
+     * and disconnects Hid device if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}.
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy determines whether hid device should be connected or disconnected
+     * @return true if hid device is connected or disconnected, false otherwise
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        try {
+            final IBluetoothHidDevice service = getService();
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                    return false;
+                }
+                return service.setConnectionPolicy(device, connectionPolicy);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    private boolean isEnabled() {
+        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+        return false;
+    }
+
+    private boolean isValidDevice(BluetoothDevice device) {
+        if (device == null) return false;
+
+        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+        return false;
+    }
+
+    private static void log(String msg) {
+        if (DBG) {
+            Log.d(TAG, msg);
+        }
+    }
 }
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index 917e7fa..4674706 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -17,7 +17,10 @@
 package android.bluetooth;
 
 import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -35,21 +38,35 @@
  *
  * @hide
  */
+@SystemApi
 public final class BluetoothMap implements BluetoothProfile {
 
     private static final String TAG = "BluetoothMap";
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
+    /** @hide */
+    @SuppressLint("ActionValue")
+    @SystemApi
     public static final String ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
 
-    /** There was an error trying to obtain the state */
+    /**
+     * There was an error trying to obtain the state
+     *
+     * @hide
+     */
     public static final int STATE_ERROR = -1;
 
+    /** @hide */
     public static final int RESULT_FAILURE = 0;
+    /** @hide */
     public static final int RESULT_SUCCESS = 1;
-    /** Connection canceled before completion. */
+    /**
+     * Connection canceled before completion.
+     *
+     * @hide
+     */
     public static final int RESULT_CANCELED = 2;
 
     private BluetoothAdapter mAdapter;
@@ -71,6 +88,7 @@
         mProfileConnector.connect(context, listener);
     }
 
+    @SuppressLint("GenericException")
     protected void finalize() throws Throwable {
         try {
             close();
@@ -84,6 +102,8 @@
      * Other public functions of BluetoothMap will return default error
      * results once close() has been called. Multiple invocations of close()
      * are ok.
+     *
+     * @hide
      */
     public synchronized void close() {
         mProfileConnector.disconnect();
@@ -98,6 +118,8 @@
      *
      * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not
      * connected to the Map service.
+     *
+     * @hide
      */
     public int getState() {
         if (VDBG) log("getState()");
@@ -120,6 +142,8 @@
      *
      * @return The remote Bluetooth device, or null if not in connected or connecting state, or if
      * this proxy object is not connected to the Map service.
+     *
+     * @hide
      */
     public BluetoothDevice getClient() {
         if (VDBG) log("getClient()");
@@ -141,6 +165,8 @@
      * Returns true if the specified Bluetooth device is connected.
      * Returns false if not connected, or if this proxy object is not
      * currently connected to the Map service.
+     *
+     * @hide
      */
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) log("isConnected(" + device + ")");
@@ -161,6 +187,8 @@
     /**
      * Initiate connection. Initiation of outgoing connections is not
      * supported for MAP server.
+     *
+     * @hide
      */
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")" + "not supported for MAPS");
@@ -172,6 +200,8 @@
      *
      * @param device Remote Bluetooth Device
      * @return false on error, true otherwise
+     *
+     * @hide
      */
     @UnsupportedAppUsage
     public boolean disconnect(BluetoothDevice device) {
@@ -196,6 +226,8 @@
      * devices. It tries to err on the side of false positives.
      *
      * @return True if this device might support Map.
+     *
+     * @hide
      */
     public static boolean doesClassMatchSink(BluetoothClass btClass) {
         // TODO optimize the rule
@@ -214,8 +246,11 @@
      * Get the list of connected devices. Currently at most one.
      *
      * @return list of connected devices
+     *
+     * @hide
      */
-    public List<BluetoothDevice> getConnectedDevices() {
+    @SystemApi
+    public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (DBG) log("getConnectedDevices()");
         final IBluetoothMap service = getService();
         if (service != null && isEnabled()) {
@@ -234,6 +269,8 @@
      * Get the list of devices matching specified states. Currently at most one.
      *
      * @return list of matching devices
+     *
+     * @hide
      */
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) log("getDevicesMatchingStates()");
@@ -254,6 +291,8 @@
      * Get connection state of device
      *
      * @return device connection state
+     *
+     * @hide
      */
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) log("getConnectionState(" + device + ")");
@@ -301,7 +340,7 @@
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
-    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+    public boolean setConnectionPolicy(@Nullable BluetoothDevice device, int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothMap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
@@ -349,7 +388,7 @@
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH)
-    public int getConnectionPolicy(BluetoothDevice device) {
+    public int getConnectionPolicy(@Nullable BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothMap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index 42f27f2..024bb06 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -16,9 +16,11 @@
 
 package android.bluetooth;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SuppressLint;
@@ -256,6 +258,41 @@
     }
 
     /**
+     * Set connection policy of the profile
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+     * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        try {
+            final IBluetoothPan service = getService();
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                    return false;
+                }
+                return service.setConnectionPolicy(device, connectionPolicy);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    /**
      * {@inheritDoc}
      */
     @Override
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index 948885e..e07ca52 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -274,15 +274,15 @@
     }
 
     /**
-     * Pbap does not store connection policy, so this function only disconnects Pbap if
-     * connectionPolicy is CONNECTION_POLICY_FORBIDDEN.
+     * Pbap does not store connection policy, so this function only disconnects pbap if
+     * connectionPolicy is {@link #CONNECTION_POLICY_FORBIDDEN}.
      *
      * <p> The device should already be paired.
      * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
      * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
      *
      * @param device Paired bluetooth device
-     * @param connectionPolicy is the connection policy to set to for this profile
+     * @param connectionPolicy determines whether to disconnect the device
      * @return true if pbap is successfully disconnected, false otherwise
      * @hide
      */
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5cb2907..44b2df6 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3240,15 +3240,40 @@
     }
 
     /**
-     * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle
-     * argument for use by system server and other multi-user aware code.
-     * @hide
+     * Binds to a service in the given {@code user} in the same manner as
+     * {@link #bindService(Intent, ServiceConnection, int)}.
+     *
+     * <p>If the given {@code user} is in the same profile group and the target package is the
+     * same as the caller, {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} is
+     * sufficient. Otherwise, requires {@code android.Manifest.permission.INTERACT_ACROSS_USERS}
+     * for interacting with other users.
+     *
+     * @param service Identifies the service to connect to.  The Intent must
+     *      specify an explicit component name.
+     * @param conn Receives information as the service is started and stopped.
+     *      This must be a valid ServiceConnection object; it must not be null.
+     * @param flags Operation options for the binding.  May be 0,
+     *          {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND},
+     *          {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT},
+     *          {@link #BIND_ALLOW_OOM_MANAGEMENT}, {@link #BIND_WAIVE_PRIORITY}.
+     *          {@link #BIND_IMPORTANT}, or
+     *          {@link #BIND_ADJUST_WITH_ACTIVITY}.
+     * @return {@code true} if the system is in the process of bringing up a
+     *         service that your client has permission to bind to; {@code false}
+     *         if the system couldn't find the service. If this value is {@code true}, you
+     *         should later call {@link #unbindService} to release the
+     *         connection.
+     *
+     * @throws SecurityException if the client does not have the required permission to bind.
      */
-    @SystemApi
     @SuppressWarnings("unused")
-    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
-    public boolean bindServiceAsUser(@RequiresPermission Intent service, ServiceConnection conn,
-            int flags, UserHandle user) {
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.INTERACT_ACROSS_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_PROFILES
+    })
+    public boolean bindServiceAsUser(
+            @NonNull @RequiresPermission Intent service, @NonNull ServiceConnection conn, int flags,
+            @NonNull UserHandle user) {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
     }
 
@@ -3391,7 +3416,6 @@
             TELEPHONY_SUBSCRIPTION_SERVICE,
             CARRIER_CONFIG_SERVICE,
             EUICC_SERVICE,
-            MMS_SERVICE,
             TELECOM_SERVICE,
             CLIPBOARD_SERVICE,
             INPUT_METHOD_SERVICE,
@@ -3588,8 +3612,6 @@
      * @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
@@ -4266,15 +4288,6 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
-     * {@link android.telephony.MmsManager} to send/receive MMS messages.
-     *
-     * @see #getSystemService(String)
-     * @see android.telephony.MmsManager
-     */
-    public static final String MMS_SERVICE = "mms";
-
-    /**
-     * Use with {@link #getSystemService(String)} to retrieve a
      * {@link android.content.ClipboardManager} for accessing and modifying
      * the contents of the global clipboard.
      *
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 3bb0f92..c8f587f 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6440,19 +6440,22 @@
      */
     public static final int FLAG_RECEIVER_NO_ABORT = 0x08000000;
     /**
-     * If set, when sending a broadcast <i>before boot has completed</i> only
+     * If set, when sending a broadcast <i>before the system has fully booted up
+     * (which is even before {@link #ACTION_LOCKED_BOOT_COMPLETED} has been sent)"</i> only
      * registered receivers will be called -- no BroadcastReceiver components
      * will be launched.  Sticky intent state will be recorded properly even
      * if no receivers wind up being called.  If {@link #FLAG_RECEIVER_REGISTERED_ONLY}
      * is specified in the broadcast intent, this flag is unnecessary.
      *
-     * <p>This flag is only for use by system sevices as a convenience to
-     * avoid having to implement a more complex mechanism around detection
+     * <p>This flag is only for use by system services (even services from mainline modules) as a
+     * convenience to avoid having to implement a more complex mechanism around detection
      * of boot completion.
      *
+     * <p>This is useful to system server mainline modules
+     *
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
     public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x04000000;
     /**
      * Set when this broadcast is for a boot upgrade, a special mode that
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index abf32c5..9d57514 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -16,15 +16,19 @@
 package android.content.pm;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 
 import com.android.internal.R;
 import com.android.internal.util.UserIcons;
@@ -226,6 +230,30 @@
         }
     }
 
+    /**
+     * Returns an {@link Intent} to open the settings page that allows the user to decide whether
+     * the calling app can interact across profiles. The current state is given by
+     * {@link #canInteractAcrossProfiles()}.
+     *
+     * <p>Returns {@code null} if {@link #canRequestInteractAcrossProfiles()} is {@code false}.
+     *
+     * @return an {@link Intent} to open the settings page that allows the user to decide whether
+     * the app can interact across profiles
+     *
+     * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the
+     * calling UID.
+     */
+    public @Nullable Intent createRequestInteractAcrossProfilesIntent() {
+        if (!canRequestInteractAcrossProfiles()) {
+            return null;
+        }
+        final Intent settingsIntent = new Intent();
+        settingsIntent.setAction(Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS);
+        final Uri packageUri = Uri.parse("package:" + mContext.getPackageName());
+        settingsIntent.setData(packageUri);
+        return settingsIntent;
+    }
+
     private void verifyCanAccessUser(UserHandle userHandle) {
         if (!getTargetUserProfiles().contains(userHandle)) {
             throw new SecurityException("Not allowed to access " + userHandle);
diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING
new file mode 100644
index 0000000..daf9a14
--- /dev/null
+++ b/core/java/android/content/res/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksResourceLoaderTests"
+    }
+  ]
+}
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 8231c58..d43a619 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -16,9 +16,11 @@
 
 package android.hardware.soundtrigger;
 
+import android.annotation.Nullable;
 import android.hardware.soundtrigger.ModelParams;
 import android.media.AudioFormat;
 import android.media.audio.common.AudioConfig;
+import android.media.soundtrigger_middleware.AudioCapabilities;
 import android.media.soundtrigger_middleware.ConfidenceLevel;
 import android.media.soundtrigger_middleware.ModelParameterRange;
 import android.media.soundtrigger_middleware.Phrase;
@@ -32,8 +34,6 @@
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
 import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
 
-import android.annotation.Nullable;
-
 import java.util.Arrays;
 import java.util.UUID;
 
@@ -48,6 +48,7 @@
                 properties.description,
                 properties.uuid,
                 properties.version,
+                properties.supportedModelArch,
                 properties.maxSoundModels,
                 properties.maxKeyPhrases,
                 properties.maxUsers,
@@ -56,7 +57,8 @@
                 properties.maxBufferMs,
                 properties.concurrentCapture,
                 properties.powerConsumptionMw,
-                properties.triggerInEvent
+                properties.triggerInEvent,
+                aidl2apiAudioCapabilities(properties.audioCapabilities)
         );
     }
 
@@ -145,6 +147,7 @@
                     apiConfig.keyphrases[i]);
         }
         aidlConfig.data = Arrays.copyOf(apiConfig.data, apiConfig.data.length);
+        aidlConfig.audioCapabilities = api2aidlAudioCapabilities(apiConfig.audioCapabilities);
         return aidlConfig;
     }
 
@@ -326,4 +329,26 @@
         }
         return new SoundTrigger.ModelParamRange(aidlRange.minInclusive, aidlRange.maxInclusive);
     }
+
+    public static int aidl2apiAudioCapabilities(int aidlCapabilities) {
+        int result = 0;
+        if ((aidlCapabilities & AudioCapabilities.ECHO_CANCELLATION) != 0) {
+            result |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+        }
+        if ((aidlCapabilities & AudioCapabilities.NOISE_SUPPRESSION) != 0) {
+            result |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+        }
+        return result;
+    }
+
+    public static int api2aidlAudioCapabilities(int apiCapabilities) {
+        int result = 0;
+        if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION) != 0) {
+            result |= AudioCapabilities.ECHO_CANCELLATION;
+        }
+        if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION) != 0) {
+            result |= AudioCapabilities.NOISE_SUPPRESSION;
+        }
+        return result;
+    }
 }
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 55505ba..60e466e 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -24,6 +24,7 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -42,6 +43,8 @@
 import android.os.ServiceManager;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.UUID;
@@ -83,6 +86,30 @@
      *
      ****************************************************************************/
     public static final class ModuleProperties implements Parcelable {
+
+        /**
+         * Bit field values of AudioCapabilities supported by the implemented HAL
+         * driver.
+         * @hide
+         */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = {
+                CAPABILITY_ECHO_CANCELLATION,
+                CAPABILITY_NOISE_SUPPRESSION
+        })
+        public @interface AudioCapabilities {}
+
+        /**
+         * If set the underlying module supports AEC.
+         * Describes bit field {@link ModuleProperties#audioCapabilities}
+         */
+        public static final int CAPABILITY_ECHO_CANCELLATION = 0x1;
+        /**
+         * If set, the underlying module supports noise suppression.
+         * Describes bit field {@link ModuleProperties#audioCapabilities}
+         */
+        public static final int CAPABILITY_NOISE_SUPPRESSION = 0x2;
+
         /** Unique module ID provided by the native service */
         public final int id;
 
@@ -101,6 +128,14 @@
         /** Voice detection engine version */
         public final int version;
 
+        /**
+         * String naming the architecture used for running the supported models.
+         * (eg. a platform running models on a DSP could implement this string to convey the DSP
+         * architecture used)
+         */
+        @NonNull
+        public final String supportedModelArch;
+
         /** Maximum number of active sound models */
         public final int maxSoundModels;
 
@@ -129,16 +164,25 @@
          * recognition callback event */
         public final boolean returnsTriggerInEvent;
 
+        /**
+         * Bit field encoding of the AudioCapabilities
+         * supported by the firmware.
+         */
+        @AudioCapabilities
+        public final int audioCapabilities;
+
         ModuleProperties(int id, @NonNull String implementor, @NonNull String description,
-                @NonNull String uuid, int version, int maxSoundModels, int maxKeyphrases,
-                int maxUsers, int recognitionModes, boolean supportsCaptureTransition,
-                int maxBufferMs, boolean supportsConcurrentCapture,
-                int powerConsumptionMw, boolean returnsTriggerInEvent) {
+                @NonNull String uuid, int version, @NonNull String supportedModelArch,
+                int maxSoundModels, int maxKeyphrases, int maxUsers, int recognitionModes,
+                boolean supportsCaptureTransition, int maxBufferMs,
+                boolean supportsConcurrentCapture, int powerConsumptionMw,
+                boolean returnsTriggerInEvent, int audioCapabilities) {
             this.id = id;
             this.implementor = requireNonNull(implementor);
             this.description = requireNonNull(description);
             this.uuid = UUID.fromString(requireNonNull(uuid));
             this.version = version;
+            this.supportedModelArch = requireNonNull(supportedModelArch);
             this.maxSoundModels = maxSoundModels;
             this.maxKeyphrases = maxKeyphrases;
             this.maxUsers = maxUsers;
@@ -148,6 +192,7 @@
             this.supportsConcurrentCapture = supportsConcurrentCapture;
             this.powerConsumptionMw = powerConsumptionMw;
             this.returnsTriggerInEvent = returnsTriggerInEvent;
+            this.audioCapabilities = audioCapabilities;
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<ModuleProperties> CREATOR
@@ -167,6 +212,7 @@
             String description = in.readString();
             String uuid = in.readString();
             int version = in.readInt();
+            String supportedModelArch = in.readString();
             int maxSoundModels = in.readInt();
             int maxKeyphrases = in.readInt();
             int maxUsers = in.readInt();
@@ -176,10 +222,11 @@
             boolean supportsConcurrentCapture = in.readByte() == 1;
             int powerConsumptionMw = in.readInt();
             boolean returnsTriggerInEvent = in.readByte() == 1;
+            int audioCapabilities = in.readInt();
             return new ModuleProperties(id, implementor, description, uuid, version,
-                    maxSoundModels, maxKeyphrases, maxUsers, recognitionModes,
+                    supportedModelArch, maxSoundModels, maxKeyphrases, maxUsers, recognitionModes,
                     supportsCaptureTransition, maxBufferMs, supportsConcurrentCapture,
-                    powerConsumptionMw, returnsTriggerInEvent);
+                    powerConsumptionMw, returnsTriggerInEvent, audioCapabilities);
         }
 
         @Override
@@ -189,6 +236,7 @@
             dest.writeString(description);
             dest.writeString(uuid.toString());
             dest.writeInt(version);
+            dest.writeString(supportedModelArch);
             dest.writeInt(maxSoundModels);
             dest.writeInt(maxKeyphrases);
             dest.writeInt(maxUsers);
@@ -198,6 +246,7 @@
             dest.writeByte((byte) (supportsConcurrentCapture ? 1 : 0));
             dest.writeInt(powerConsumptionMw);
             dest.writeByte((byte) (returnsTriggerInEvent ? 1 : 0));
+            dest.writeInt(audioCapabilities);
         }
 
         @Override
@@ -208,13 +257,15 @@
         @Override
         public String toString() {
             return "ModuleProperties [id=" + id + ", implementor=" + implementor + ", description="
-                    + description + ", uuid=" + uuid + ", version=" + version + ", maxSoundModels="
+                    + description + ", uuid=" + uuid + ", version=" + version
+                    + " , supportedModelArch=" + supportedModelArch + ", maxSoundModels="
                     + maxSoundModels + ", maxKeyphrases=" + maxKeyphrases + ", maxUsers="
                     + maxUsers + ", recognitionModes=" + recognitionModes
                     + ", supportsCaptureTransition=" + supportsCaptureTransition + ", maxBufferMs="
                     + maxBufferMs + ", supportsConcurrentCapture=" + supportsConcurrentCapture
                     + ", powerConsumptionMw=" + powerConsumptionMw
-                    + ", returnsTriggerInEvent=" + returnsTriggerInEvent + "]";
+                    + ", returnsTriggerInEvent=" + returnsTriggerInEvent
+                    + ", audioCapabilities=" + audioCapabilities + "]";
         }
     }
 
@@ -588,19 +639,19 @@
         }
     }
 
-    /*****************************************************************************
+    /**
      * A ModelParamRange is a representation of supported parameter range for a
      * given loaded model.
-     ****************************************************************************/
+     */
     public static final class ModelParamRange implements Parcelable {
 
         /**
-         * start of supported range inclusive
+         * The inclusive start of supported range.
          */
         public final int start;
 
         /**
-         * end of supported range inclusive
+         * The inclusive end of supported range.
          */
         public final int end;
 
@@ -609,31 +660,65 @@
             this.end = end;
         }
 
+        /** @hide */
         private ModelParamRange(@NonNull Parcel in) {
             this.start = in.readInt();
             this.end = in.readInt();
         }
 
         @NonNull
-        public static final Creator<ModelParamRange> CREATOR = new Creator<ModelParamRange>() {
-            @Override
-            @NonNull
-            public ModelParamRange createFromParcel(@NonNull Parcel in) {
-                return new ModelParamRange(in);
-            }
+        public static final Creator<ModelParamRange> CREATOR =
+                new Creator<ModelParamRange>() {
+                    @Override
+                    @NonNull
+                    public ModelParamRange createFromParcel(@NonNull Parcel in) {
+                        return new ModelParamRange(in);
+                    }
 
-            @Override
-            @NonNull
-            public ModelParamRange[] newArray(int size) {
-                return new ModelParamRange[size];
-            }
-        };
+                    @Override
+                    @NonNull
+                    public ModelParamRange[] newArray(int size) {
+                        return new ModelParamRange[size];
+                    }
+                };
 
+        /** @hide */
         @Override
         public int describeContents() {
             return 0;
         }
 
+        /** @hide */
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (start);
+            result = prime * result + (end);
+            return result;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            ModelParamRange other = (ModelParamRange) obj;
+            if (start != other.start) {
+                return false;
+            }
+            if (end != other.end) {
+                return false;
+            }
+            return true;
+        }
+
         @Override
         public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeInt(start);
@@ -1002,13 +1087,27 @@
         @NonNull
         public final byte[] data;
 
-        @UnsupportedAppUsage
+        /**
+         * Bit field encoding of the AudioCapabilities
+         * supported by the firmware.
+         */
+        @ModuleProperties.AudioCapabilities
+        public final int audioCapabilities;
+
         public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
-                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
+                int audioCapabilities) {
             this.captureRequested = captureRequested;
             this.allowMultipleTriggers = allowMultipleTriggers;
             this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
             this.data = data != null ? data : new byte[0];
+            this.audioCapabilities = audioCapabilities;
+        }
+
+        @UnsupportedAppUsage
+        public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
+                @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+            this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR
@@ -1028,7 +1127,9 @@
             KeyphraseRecognitionExtra[] keyphrases =
                     in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
             byte[] data = in.readBlob();
-            return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data);
+            int audioCapabilities = in.readInt();
+            return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data,
+                    audioCapabilities);
         }
 
         @Override
@@ -1037,6 +1138,7 @@
             dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0));
             dest.writeTypedArray(keyphrases, flags);
             dest.writeBlob(data);
+            dest.writeInt(audioCapabilities);
         }
 
         @Override
@@ -1048,7 +1150,8 @@
         public String toString() {
             return "RecognitionConfig [captureRequested=" + captureRequested
                     + ", allowMultipleTriggers=" + allowMultipleTriggers + ", keyphrases="
-                    + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data) + "]";
+                    + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data)
+                    + ", audioCapabilities=" + Integer.toHexString(audioCapabilities) + "]";
         }
     }
 
diff --git a/core/java/android/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/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/os/Process.java b/core/java/android/os/Process.java
index 5fe647c..5d80ab6 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -68,10 +68,9 @@
     public static final int LOG_UID = 1007;
 
     /**
-     * Defines the UID/GID for the WIFI supplicant process.
-     * @hide
+     * Defines the UID/GID for the WIFI native processes like wificond, supplicant, hostapd,
+     * vendor HAL, etc.
      */
-    @UnsupportedAppUsage
     public static final int WIFI_UID = 1010;
 
     /**
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 6e199ce3..d8fadfb 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1850,10 +1850,7 @@
      * Checks if the calling app is running in a managed profile.
      *
      * @return whether the caller is in a managed profile.
-     * @hide
      */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
     public boolean isManagedProfile() {
         // No need for synchronization.  Once it becomes non-null, it'll be non-null forever.
         // Worst case we might end up calling the AIDL method multiple times but that's fine.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e08f966..a31c3d1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -389,6 +389,21 @@
             "android.settings.MANAGE_UNKNOWN_APP_SOURCES";
 
     /**
+     * Activity Action: Show settings to allow configuration of cross-profile access for apps
+     *
+     * Input: Optionally, the Intent's data URI can specify the application package name to
+     * directly invoke the management GUI specific to the package name. For example
+     * "package:com.my.app".
+     * <p>
+     * Output: Nothing.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_CROSS_PROFILE_ACCESS =
+            "android.settings.MANAGE_CROSS_PROFILE_ACCESS";
+
+    /**
      * Activity Action: Show the "Open by Default" page in a particular application's details page.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
@@ -14465,8 +14480,9 @@
      * current time.
      * @hide
      */
-    public static boolean checkAndNoteWriteSettingsOperation(Context context, int uid,
-            String callingPackage, boolean throwException) {
+    @SystemApi
+    public static boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid,
+            @NonNull String callingPackage, boolean throwException) {
         return isCallingPackageAllowedToPerformAppOpsProtectedOperation(context, uid,
                 callingPackage, throwException, AppOpsManager.OP_WRITE_SETTINGS,
                 PM_WRITE_SETTINGS, true);
diff --git a/core/java/android/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/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index 5977baf..4ead3fc 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -49,6 +49,9 @@
     void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel);
     void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId);
     void onNotificationsSeen(in List<String> keys);
+    void onPanelRevealed(int items);
+    void onPanelHidden();
+    void onNotificationVisibilityChanged(String key, boolean isVisible);
     void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded);
     void onNotificationDirectReply(String key);
     void onSuggestedReplySent(String key, in CharSequence reply, int source);
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index da40254..e976e18 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -182,6 +182,32 @@
     }
 
     /**
+     * Implement this to know when the notification panel is revealed
+     *
+     * @param items Number of items on the panel at time of opening
+     */
+    public void onPanelRevealed(int items) {
+
+    }
+
+    /**
+     * Implement this to know when the notification panel is hidden
+     */
+    public void onPanelHidden() {
+
+    }
+
+    /**
+     * Implement this to know when a notification becomes visible or hidden from the user.
+     *
+     * @param key the notification key
+     * @param isVisible whether the notification is visible.
+     */
+    public void onNotificationVisibilityChanged(@NonNull String key, boolean isVisible) {
+
+    }
+
+    /**
      * Implement this to know when a notification change (expanded / collapsed) is visible to user.
      *
      * @param key the notification key
@@ -337,6 +363,30 @@
         }
 
         @Override
+        public void onPanelRevealed(int items) {
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = items;
+            mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_REVEALED,
+                    args).sendToTarget();
+        }
+
+        @Override
+        public void onPanelHidden() {
+            SomeArgs args = SomeArgs.obtain();
+            mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_HIDDEN,
+                    args).sendToTarget();
+        }
+
+        @Override
+        public void onNotificationVisibilityChanged(String key, boolean isVisible) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = key;
+            args.argi1 = isVisible ? 1 : 0;
+            mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_VISIBILITY_CHANGED,
+                    args).sendToTarget();
+        }
+
+        @Override
         public void onNotificationExpansionChanged(String key, boolean isUserAction,
                 boolean isExpanded) {
             SomeArgs args = SomeArgs.obtain();
@@ -394,6 +444,9 @@
         public static final int MSG_ON_SUGGESTED_REPLY_SENT = 6;
         public static final int MSG_ON_ACTION_INVOKED = 7;
         public static final int MSG_ON_ALLOWED_ADJUSTMENTS_CHANGED = 8;
+        public static final int MSG_ON_PANEL_REVEALED = 9;
+        public static final int MSG_ON_PANEL_HIDDEN = 10;
+        public static final int MSG_ON_NOTIFICATION_VISIBILITY_CHANGED = 11;
 
         public MyHandler(Looper looper) {
             super(looper, null, false);
@@ -480,6 +533,25 @@
                     onAllowedAdjustmentsChanged();
                     break;
                 }
+                case MSG_ON_PANEL_REVEALED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    int items = args.argi1;
+                    args.recycle();
+                    onPanelRevealed(items);
+                    break;
+                }
+                case MSG_ON_PANEL_HIDDEN: {
+                    onPanelHidden();
+                    break;
+                }
+                case MSG_ON_NOTIFICATION_VISIBILITY_CHANGED: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    String key = (String) args.arg1;
+                    boolean isVisible = args.argi1 == 1;
+                    args.recycle();
+                    onNotificationVisibilityChanged(key, isVisible);
+                    break;
+                }
             }
         }
     }
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 80d054b..fd04f49 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1383,6 +1383,22 @@
         }
 
         @Override
+        public void onPanelRevealed(int items) throws RemoteException {
+            // no-op in the listener
+        }
+
+        @Override
+        public void onPanelHidden() throws RemoteException {
+            // no-op in the listener
+        }
+
+        @Override
+        public void onNotificationVisibilityChanged(
+                String key, boolean isVisible) {
+            // no-op in the listener
+        }
+
+        @Override
         public void onNotificationSnoozedUntilContext(
                 IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId)
                 throws RemoteException {
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 67925bf..0f33998 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -119,7 +119,9 @@
     @IntDef(flag = true, prefix = { "RECOGNITION_FLAG_" }, value = {
             RECOGNITION_FLAG_NONE,
             RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO,
-            RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
+            RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS,
+            RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION,
+            RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION,
     })
     public @interface RecognitionFlags {}
 
@@ -144,6 +146,26 @@
      */
     public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2;
 
+    /**
+     * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+     * if the underlying recognition should use AEC.
+     * This capability may or may not be supported by the system, and support can be queried
+     * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for
+     * this flag is {@link #AUDIO_CAPABILITY_ECHO_CANCELLATION}. If this flag is passed without the
+     * audio capability supported, there will be no audio effect applied.
+     */
+    public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 0x4;
+
+    /**
+     * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+     * if the underlying recognition should use noise suppression.
+     * This capability may or may not be supported by the system, and support can be queried
+     * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for
+     * this flag is {@link #AUDIO_CAPABILITY_NOISE_SUPPRESSION}. If this flag is passed without the
+     * audio capability supported, there will be no audio effect applied.
+     */
+    public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8;
+
     //---- Recognition mode flags. Return codes for getSupportedRecognitionModes() ----//
     // Must be kept in sync with the related attribute defined as searchKeyphraseRecognitionFlags.
 
@@ -168,6 +190,46 @@
     public static final int RECOGNITION_MODE_USER_IDENTIFICATION
             = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
 
+    //-- Audio capabilities. Values in returned bit field for getSupportedAudioCapabilities() --//
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = {
+            AUDIO_CAPABILITY_ECHO_CANCELLATION,
+            AUDIO_CAPABILITY_NOISE_SUPPRESSION,
+    })
+    public @interface AudioCapabilities {}
+
+    /**
+     * If set the underlying module supports AEC.
+     * Returned by {@link #getSupportedAudioCapabilities()}
+     */
+    public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION =
+            SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+
+    /**
+     * If set, the underlying module supports noise suppression.
+     * Returned by {@link #getSupportedAudioCapabilities()}
+     */
+    public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION =
+            SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "MODEL_PARAM_" }, value = {
+            MODEL_PARAM_THRESHOLD_FACTOR,
+    })
+    public @interface ModelParams {}
+
+    /**
+     * Controls the sensitivity threshold adjustment factor for a given model.
+     * Negative value corresponds to less sensitive model (high threshold) and
+     * a positive value corresponds to a more sensitive model (low threshold).
+     * Default value is 0.
+     */
+    public static final int MODEL_PARAM_THRESHOLD_FACTOR =
+            android.hardware.soundtrigger.ModelParams.THRESHOLD_FACTOR;
+
     static final String TAG = "AlwaysOnHotwordDetector";
     static final boolean DBG = false;
 
@@ -198,6 +260,53 @@
     private int mAvailability = STATE_NOT_READY;
 
     /**
+     *  A ModelParamRange is a representation of supported parameter range for a
+     *  given loaded model.
+     */
+    public static final class ModelParamRange {
+        private final SoundTrigger.ModelParamRange mModelParamRange;
+
+        /** @hide */
+        ModelParamRange(SoundTrigger.ModelParamRange modelParamRange) {
+            mModelParamRange = modelParamRange;
+        }
+
+        /**
+         * The inclusive start of supported range.
+         *
+         * @return start of range
+         */
+        public int start() {
+            return mModelParamRange.start;
+        }
+
+        /**
+         * The inclusive end of supported range.
+         *
+         * @return end of range
+         */
+        public int end() {
+            return mModelParamRange.end;
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return mModelParamRange.toString();
+        }
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            return mModelParamRange.equals(obj);
+        }
+
+        @Override
+        public int hashCode() {
+            return mModelParamRange.hashCode();
+        }
+    }
+
+    /**
      * Additional payload for {@link Callback#onDetected}.
      */
     public static class EventPayload {
@@ -385,6 +494,37 @@
     }
 
     /**
+     * Get the audio capabilities supported by the platform which can be enabled when
+     * starting a recognition.
+     *
+     * @see #AUDIO_CAPABILITY_ECHO_CANCELLATION
+     * @see #AUDIO_CAPABILITY_NOISE_SUPPRESSION
+     *
+     * @return Bit field encoding of the AudioCapabilities supported.
+     */
+    @AudioCapabilities
+    public int getSupportedAudioCapabilities() {
+        if (DBG) Slog.d(TAG, "getSupportedAudioCapabilities()");
+        synchronized (mLock) {
+            return getSupportedAudioCapabilitiesLocked();
+        }
+    }
+
+    private int getSupportedAudioCapabilitiesLocked() {
+        try {
+            ModuleProperties properties =
+                    mModelManagementService.getDspModuleProperties(mVoiceInteractionService);
+            if (properties != null) {
+                return properties.audioCapabilities;
+            }
+
+            return 0;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Starts recognition for the associated keyphrase.
      *
      * @see #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
@@ -445,6 +585,83 @@
     }
 
     /**
+     * Set a model specific {@link ModelParams} with the given value. This
+     * parameter will keep its value for the duration the model is loaded regardless of starting and
+     * stopping recognition. Once the model is unloaded, the value will be lost.
+     * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before calling this
+     * method.
+     *
+     * @param modelParam   {@link ModelParams}
+     * @param value        Value to set
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+     *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+     *           if API is not supported by HAL
+     */
+    public int setParameter(@ModelParams int modelParam, int value) {
+        if (DBG) {
+            Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")");
+        }
+
+        synchronized (mLock) {
+            if (mAvailability == STATE_INVALID) {
+                throw new IllegalStateException("setParameter called on an invalid detector");
+            }
+
+            return setParameterLocked(modelParam, value);
+        }
+    }
+
+    /**
+     * Get a model specific {@link ModelParams}. This parameter will keep its value
+     * for the duration the model is loaded regardless of starting and stopping recognition.
+     * Once the model is unloaded, the value will be lost. If the value is not set, a default
+     * value is returned. See {@link ModelParams} for parameter default values.
+     * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before
+     * calling this method.
+     *
+     * @param modelParam   {@link ModelParams}
+     * @return value of parameter
+     */
+    public int getParameter(@ModelParams int modelParam) {
+        if (DBG) {
+            Slog.d(TAG, "getParameter(" + modelParam + ")");
+        }
+
+        synchronized (mLock) {
+            if (mAvailability == STATE_INVALID) {
+                throw new IllegalStateException("getParameter called on an invalid detector");
+            }
+
+            return getParameterLocked(modelParam);
+        }
+    }
+
+    /**
+     * Determine if parameter control is supported for the given model handle.
+     * This method should be checked prior to calling {@link AlwaysOnHotwordDetector#setParameter}
+     * or {@link AlwaysOnHotwordDetector#getParameter}.
+     *
+     * @param modelParam {@link ModelParams}
+     * @return supported range of parameter, null if not supported
+     */
+    @Nullable
+    public ModelParamRange queryParameter(@ModelParams int modelParam) {
+        if (DBG) {
+            Slog.d(TAG, "queryParameter(" + modelParam + ")");
+        }
+
+        synchronized (mLock) {
+            if (mAvailability == STATE_INVALID) {
+                throw new IllegalStateException("queryParameter called on an invalid detector");
+            }
+
+            return queryParameterLocked(modelParam);
+        }
+    }
+
+    /**
      * Creates an intent to start the enrollment for the associated keyphrase.
      * This intent must be invoked using {@link Context#startForegroundService(Intent)}.
      * Starting re-enrollment is only valid if the keyphrase is un-enrolled,
@@ -571,12 +788,21 @@
                 (recognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;
         boolean allowMultipleTriggers =
                 (recognitionFlags&RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0;
+
+        int audioCapabilities = 0;
+        if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) {
+            audioCapabilities |= AUDIO_CAPABILITY_ECHO_CANCELLATION;
+        }
+        if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) {
+            audioCapabilities |= AUDIO_CAPABILITY_NOISE_SUPPRESSION;
+        }
+
         int code = STATUS_ERROR;
         try {
             code = mModelManagementService.startRecognition(mVoiceInteractionService,
                     mKeyphraseMetadata.id, mLocale.toLanguageTag(), mInternalCallback,
                     new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
-                            recognitionExtra, null /* additional data */));
+                            recognitionExtra, null /* additional data */, audioCapabilities));
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException in startRecognition!", e);
         }
@@ -601,6 +827,47 @@
         return code;
     }
 
+    private int setParameterLocked(@ModelParams int modelParam, int value) {
+        try {
+            int code = mModelManagementService.setParameter(mVoiceInteractionService,
+                    mKeyphraseMetadata.id, modelParam, value);
+
+            if (code != STATUS_OK) {
+                Slog.w(TAG, "setParameter failed with error code " + code);
+            }
+
+            return code;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private int getParameterLocked(@ModelParams int modelParam) {
+        try {
+            return mModelManagementService.getParameter(mVoiceInteractionService,
+                    mKeyphraseMetadata.id, modelParam);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Nullable
+    private ModelParamRange queryParameterLocked(@ModelParams int modelParam) {
+        try {
+            SoundTrigger.ModelParamRange modelParamRange =
+                    mModelManagementService.queryParameter(mVoiceInteractionService,
+                            mKeyphraseMetadata.id, modelParam);
+
+            if (modelParamRange == null) {
+                return null;
+            }
+
+            return new ModelParamRange(modelParamRange);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private void notifyStateChangedLocked() {
         Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED);
         message.arg1 = mAvailability;
diff --git a/core/java/android/view/ITaskOrganizer.aidl b/core/java/android/view/ITaskOrganizer.aidl
new file mode 100644
index 0000000..e92aafe
--- /dev/null
+++ b/core/java/android/view/ITaskOrganizer.aidl
@@ -0,0 +1,38 @@
+/* //device/java/android/android/view/ITaskOrganizer.aidl
+**
+** Copyright 2019, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.view;
+
+import android.view.IWindowContainer;
+import android.view.SurfaceControl;
+import android.app.ActivityManager;
+
+/**
+ * Interface for ActivityTaskManager/WindowManager to delegate control of tasks.
+ * {@hide}
+ */
+oneway interface ITaskOrganizer {
+    void taskAppeared(in IWindowContainer container,
+        in ActivityManager.RunningTaskInfo taskInfo);
+    void taskVanished(in IWindowContainer container);
+
+    /**
+     * Called upon completion of
+     * ActivityTaskManagerService#applyTaskOrganizerTransaction
+     */
+    void transactionReady(int id, in SurfaceControl.Transaction t);
+}
\ No newline at end of file
diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java
index 6fdadc6..27edb0b 100644
--- a/core/java/android/view/InsetsAnimationControlCallbacks.java
+++ b/core/java/android/view/InsetsAnimationControlCallbacks.java
@@ -16,17 +16,28 @@
 
 package android.view;
 
+import android.view.InsetsController.LayoutInsetsDuringAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
+
 /**
  * Provide an interface to let InsetsAnimationControlImpl call back into its owner.
  * @hide
  */
 public interface InsetsAnimationControlCallbacks {
+
     /**
-     * Dispatch the animation started event to all listeners.
-     * @param animation
+     * Executes the necessary code to start the animation in the correct order, including:
+     * <ul>
+     *     <li>Dispatch {@link WindowInsetsAnimationCallback#onPrepare}</li>
+     *     <li>Update insets state and run layout according to {@code layoutDuringAnimation}</li>
+     *     <li>Dispatch {@link WindowInsetsAnimationCallback#onStart}</li>
+     *     <li>Dispatch {@link WindowInsetsAnimationControlListener#onReady}</li>
+     * </ul>
      */
-    void dispatchAnimationStarted(WindowInsetsAnimationCallback.InsetsAnimation animation,
-            WindowInsetsAnimationCallback.AnimationBounds bounds);
+    void startAnimation(InsetsAnimationControlImpl controller,
+            WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation,
+            AnimationBounds bounds, @LayoutInsetsDuringAnimation int layoutDuringAnimation);
 
     /**
      * Schedule the apply by posting the animation callback.
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index a3245b9..6589e75 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 import static android.view.InsetsState.ISIDE_BOTTOM;
 import static android.view.InsetsState.ISIDE_FLOATING;
 import static android.view.InsetsState.ISIDE_LEFT;
@@ -30,6 +32,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.SparseSetArray;
+import android.view.InsetsController.LayoutInsetsDuringAnimation;
 import android.view.InsetsState.InternalInsetsSide;
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.WindowInsets.Type.InsetsType;
@@ -80,7 +83,8 @@
     public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, Rect frame,
             InsetsState state, WindowInsetsAnimationControlListener listener,
             @InsetsType int types,
-            InsetsAnimationControlCallbacks controller, long durationMs, boolean fade) {
+            InsetsAnimationControlCallbacks controller, long durationMs, boolean fade,
+            @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) {
         mControls = controls;
         mListener = listener;
         mTypes = types;
@@ -95,14 +99,11 @@
         mFrame = new Rect(frame);
         buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mControls);
 
-        // TODO: Check for controllability first and wait for IME if needed.
-        listener.onReady(this, types);
-
         mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes,
                 InsetsController.INTERPOLATOR, durationMs);
         mAnimation.setAlpha(getCurrentAlpha());
-        mController.dispatchAnimationStarted(mAnimation,
-                new AnimationBounds(mHiddenInsets, mShownInsets));
+        mController.startAnimation(this, listener, types, mAnimation,
+                new AnimationBounds(mHiddenInsets, mShownInsets), layoutInsetsDuringAnimation);
     }
 
     @Override
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 2a7a4e3..775490c 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -37,16 +37,20 @@
 import android.view.InsetsSourceConsumer.ShowResult;
 import android.view.InsetsState.InternalInsetsType;
 import android.view.SurfaceControl.Transaction;
+import android.view.ViewTreeObserver.OnPreDrawListener;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimationCallback.AnimationBounds;
 import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 
 /**
@@ -67,6 +71,37 @@
     private @interface AnimationDirection{}
 
     /**
+     * Layout mode during insets animation: The views should be laid out as if the changing inset
+     * types are fully shown. Before starting the animation, {@link View#onApplyWindowInsets} will
+     * be called as if the changing insets types are shown, which will result in the views being
+     * laid out as if the insets are fully shown.
+     */
+    static final int LAYOUT_INSETS_DURING_ANIMATION_SHOWN = 0;
+
+    /**
+     * Layout mode during insets animation: The views should be laid out as if the changing inset
+     * types are fully hidden. Before starting the animation, {@link View#onApplyWindowInsets} will
+     * be called as if the changing insets types are hidden, which will result in the views being
+     * laid out as if the insets are fully hidden.
+     */
+    static final int LAYOUT_INSETS_DURING_ANIMATION_HIDDEN = 1;
+
+    /**
+     * Determines the behavior of how the views should be laid out during an insets animation that
+     * is controlled by the application by calling {@link #controlWindowInsetsAnimation}.
+     * <p>
+     * When the animation is system-initiated, the layout mode is always chosen such that the
+     * pre-animation layout will represent the opposite of the starting state, i.e. when insets
+     * are appearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_SHOWN} will be used. When insets
+     * are disappearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_HIDDEN} will be used.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {LAYOUT_INSETS_DURING_ANIMATION_SHOWN,
+            LAYOUT_INSETS_DURING_ANIMATION_HIDDEN})
+    @interface LayoutInsetsDuringAnimation {
+    }
+
+    /**
      * Translation animation evaluator.
      */
     private static TypeEvaluator<Insets> sEvaluator = (fraction, startValue, endValue) -> Insets.of(
@@ -109,11 +144,7 @@
         @Override
         public void onReady(WindowInsetsAnimationController controller, int types) {
             mController = controller;
-            if (mShow) {
-                showDirectly(types);
-            } else {
-                hideDirectly(types);
-            }
+
             mAnimationDirection = mShow ? DIRECTION_SHOW : DIRECTION_HIDE;
             mAnimator = ObjectAnimator.ofObject(
                     controller,
@@ -131,7 +162,9 @@
                     onAnimationFinish();
                 }
             });
+            mStartingAnimation = true;
             mAnimator.start();
+            mStartingAnimation = false;
         }
 
         @Override
@@ -185,6 +218,7 @@
     private int mPendingTypesToShow;
 
     private int mLastLegacySoftInputMode;
+    private boolean mStartingAnimation;
 
     private SyncRtSurfaceTransactionApplier mApplier;
 
@@ -266,6 +300,14 @@
     }
 
     /**
+     * @see InsetsState#calculateVisibleInsets(Rect, Rect, int)
+     */
+    public Rect calculateVisibleInsets(Rect legacyVisibleInsets,
+            @SoftInputModeFlags int softInputMode) {
+        return mState.calculateVisibleInsets(mFrame, legacyVisibleInsets, softInputMode);
+    }
+
+    /**
      * Called when the server has dispatched us a new set of inset controls.
      */
     public void onControlsChanged(InsetsSourceControl[] activeControls) {
@@ -312,7 +354,7 @@
                 // Only one animator (with multiple InsetsType) can run at a time.
                 // previous one should be cancelled for simplicity.
                 cancelExistingAnimation();
-            } else if (consumer.isVisible()
+            } else if (consumer.isRequestedVisible()
                     && (mAnimationDirection == DIRECTION_NONE
                     || mAnimationDirection == DIRECTION_HIDE)) {
                 // no-op: already shown or animating in (because window visibility is
@@ -338,7 +380,7 @@
             InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
             if (mAnimationDirection == DIRECTION_SHOW) {
                 cancelExistingAnimation();
-            } else if (!consumer.isVisible()
+            } else if (!consumer.isRequestedVisible()
                     && (mAnimationDirection == DIRECTION_NONE
                     || mAnimationDirection == DIRECTION_HIDE)) {
                 // no-op: already hidden or animating out.
@@ -363,12 +405,14 @@
             listener.onCancelled();
             return;
         }
-        controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */);
+        controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */,
+                getLayoutInsetsDuringAnimationMode(types));
     }
 
     private void controlAnimationUnchecked(@InsetsType int types,
             WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
-            long durationMs, boolean fade) {
+            long durationMs, boolean fade,
+            @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) {
         if (types == 0) {
             // nothing to animate.
             return;
@@ -398,7 +442,8 @@
         }
 
         final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(controls,
-                frame, mState, listener, typesReady, this, durationMs, fade);
+                frame, mState, listener, typesReady, this, durationMs, fade,
+                layoutInsetsDuringAnimation);
         mAnimationControls.add(controller);
     }
 
@@ -412,7 +457,7 @@
         boolean isReady = true;
         for (int i = internalTypes.size() - 1; i >= 0; i--) {
             InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
-            boolean setVisible = !consumer.isVisible();
+            boolean setVisible = !consumer.isRequestedVisible();
             if (setVisible) {
                 // Show request
                 switch(consumer.requestShow(fromIme)) {
@@ -454,6 +499,29 @@
         return typesReady;
     }
 
+    private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode(
+            @InsetsType int types) {
+
+        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+
+        // Generally, we want to layout the opposite of the current state. This is to make animation
+        // callbacks easy to use: The can capture the layout values and then treat that as end-state
+        // during the animation.
+        //
+        // However, if controlling multiple sources, we want to treat it as shown if any of the
+        // types is currently hidden.
+        for (int i = internalTypes.size() - 1; i >= 0; i--) {
+            InsetsSourceConsumer consumer = mSourceConsumers.get(internalTypes.valueAt(i));
+            if (consumer == null) {
+                continue;
+            }
+            if (!consumer.isRequestedVisible()) {
+                return LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
+            }
+        }
+        return LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
+    }
+
     private void cancelExistingControllers(@InsetsType int types) {
         for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
             InsetsAnimationControlImpl control = mAnimationControls.get(i);
@@ -597,7 +665,9 @@
         // and hidden state insets are correct.
         controlAnimationUnchecked(
                 types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(),
-                true /* fade */);
+                true /* fade */, show
+                        ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
+                        : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN);
     }
 
     private void hideDirectly(@InsetsType int types) {
@@ -629,18 +699,40 @@
 
     @VisibleForTesting
     @Override
-    public void dispatchAnimationStarted(InsetsAnimation animation, AnimationBounds bounds) {
-        mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation, bounds);
+    public void startAnimation(InsetsAnimationControlImpl controller,
+            WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation,
+            AnimationBounds bounds, int layoutDuringAnimation) {
+        if (layoutDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) {
+            showDirectly(types);
+        } else {
+            hideDirectly(types);
+        }
+        mViewRoot.mView.dispatchWindowInsetsAnimationPrepare(animation);
+        mViewRoot.mView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                mViewRoot.mView.getViewTreeObserver().removeOnPreDrawListener(this);
+                mViewRoot.mView.dispatchWindowInsetsAnimationStart(animation, bounds);
+                listener.onReady(controller, types);
+                return true;
+            }
+        });
+        mViewRoot.mView.invalidate();
     }
 
     @VisibleForTesting
     public void dispatchAnimationFinished(InsetsAnimation animation) {
-        mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
+        mViewRoot.mView.dispatchWindowInsetsAnimationFinish(animation);
     }
 
     @VisibleForTesting
     @Override
     public void scheduleApplyChangeInsets() {
+        if (mStartingAnimation) {
+            mAnimCallback.run();
+            mAnimCallbackScheduled = false;
+            return;
+        }
         if (!mAnimCallbackScheduled) {
             mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION,
                     mAnimCallback, null /* token*/);
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 324d562..67ccfd6 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.Nullable;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Parcel;
@@ -23,6 +24,7 @@
 import android.view.InsetsState.InternalInsetsType;
 
 import java.io.PrintWriter;
+import java.util.Objects;
 
 /**
  * Represents the state of a single window generating insets for clients.
@@ -34,6 +36,7 @@
 
     /** Frame of the source in screen coordinate space */
     private final Rect mFrame;
+    private @Nullable Rect mVisibleFrame;
     private boolean mVisible;
 
     private final Rect mTmpFrame = new Rect();
@@ -54,6 +57,10 @@
         mFrame.set(frame);
     }
 
+    public void setVisibleFrame(@Nullable Rect visibleFrame) {
+        mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : visibleFrame;
+    }
+
     public void setVisible(boolean visible) {
         mVisible = visible;
     }
@@ -66,6 +73,10 @@
         return mFrame;
     }
 
+    public @Nullable Rect getVisibleFrame() {
+        return mVisibleFrame;
+    }
+
     public boolean isVisible() {
         return mVisible;
     }
@@ -79,10 +90,22 @@
      *         source.
      */
     public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) {
+        return calculateInsets(relativeFrame, mFrame, ignoreVisibility);
+    }
+
+    /**
+     * Like {@link #calculateInsets(Rect, boolean)}, but will return visible insets.
+     */
+    public Insets calculateVisibleInsets(Rect relativeFrame) {
+        return calculateInsets(relativeFrame, mVisibleFrame != null ? mVisibleFrame : mFrame,
+                false /* ignoreVisibility */);
+    }
+
+    private Insets calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility) {
         if (!ignoreVisibility && !mVisible) {
             return Insets.NONE;
         }
-        if (!mTmpFrame.setIntersect(mFrame, relativeFrame)) {
+        if (!mTmpFrame.setIntersect(frame, relativeFrame)) {
             return Insets.NONE;
         }
 
@@ -110,6 +133,9 @@
         pw.print(prefix);
         pw.print("InsetsSource type="); pw.print(InsetsState.typeToString(mType));
         pw.print(" frame="); pw.print(mFrame.toShortString());
+        if (mVisibleFrame != null) {
+            pw.print(" visibleFrmae="); pw.print(mVisibleFrame.toShortString());
+        }
         pw.print(" visible="); pw.print(mVisible);
         pw.println();
     }
@@ -123,6 +149,7 @@
 
         if (mType != that.mType) return false;
         if (mVisible != that.mVisible) return false;
+        if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
         return mFrame.equals(that.mFrame);
     }
 
@@ -137,6 +164,7 @@
     public InsetsSource(Parcel in) {
         mType = in.readInt();
         mFrame = in.readParcelable(null /* loader */);
+        mVisibleFrame = in.readParcelable(null /* loader */);
         mVisible = in.readBoolean();
     }
 
@@ -149,6 +177,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mType);
         dest.writeParcelable(mFrame, 0 /* flags*/);
+        dest.writeParcelable(mVisibleFrame, 0 /* flags */);
         dest.writeBoolean(mVisible);
     }
 
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index c6d9898..b2a5d91 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -53,7 +53,7 @@
     }
 
     protected final InsetsController mController;
-    protected boolean mVisible;
+    protected boolean mRequestedVisible;
     private final Supplier<Transaction> mTransactionSupplier;
     private final @InternalInsetsType int mType;
     private final InsetsState mState;
@@ -66,7 +66,7 @@
         mState = state;
         mTransactionSupplier = transactionSupplier;
         mController = controller;
-        mVisible = InsetsState.getDefaultVisibility(type);
+        mRequestedVisible = InsetsState.getDefaultVisibility(type);
     }
 
     public void setControl(@Nullable InsetsSourceControl control) {
@@ -94,12 +94,12 @@
 
     @VisibleForTesting
     public void show() {
-        setVisible(true);
+        setRequestedVisible(true);
     }
 
     @VisibleForTesting
     public void hide() {
-        setVisible(false);
+        setRequestedVisible(false);
     }
 
     /**
@@ -126,16 +126,16 @@
         if (mSourceControl == null) {
             return false;
         }
-        if (mState.getSource(mType).isVisible() == mVisible) {
+        if (mState.getSource(mType).isVisible() == mRequestedVisible) {
             return false;
         }
-        mState.getSource(mType).setVisible(mVisible);
+        mState.getSource(mType).setVisible(mRequestedVisible);
         return true;
     }
 
     @VisibleForTesting
-    public boolean isVisible() {
-        return mVisible;
+    public boolean isRequestedVisible() {
+        return mRequestedVisible;
     }
 
     /**
@@ -157,11 +157,15 @@
         // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
     }
 
-    private void setVisible(boolean visible) {
-        if (mVisible == visible) {
+    /**
+     * Sets requested visibility from the client, regardless of whether we are able to control it at
+     * the moment.
+     */
+    private void setRequestedVisible(boolean requestedVisible) {
+        if (mRequestedVisible == requestedVisible) {
             return;
         }
-        mVisible = visible;
+        mRequestedVisible = requestedVisible;
         applyLocalVisibilityOverride();
         mController.notifyVisibilityChanged();
     }
@@ -173,7 +177,7 @@
         }
 
         final Transaction t = mTransactionSupplier.get();
-        if (mVisible) {
+        if (mRequestedVisible) {
             t.show(mSourceControl.getLeash());
         } else {
             t.hide(mSourceControl.getLeash());
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index ae1e579..e33ca70 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -19,12 +19,14 @@
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+import static android.view.ViewRootImpl.sNewInsetsMode;
 import static android.view.WindowInsets.Type.IME;
 import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
 import static android.view.WindowInsets.Type.SIZE;
 import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.indexOf;
+import static android.view.WindowInsets.Type.isVisibleInsetsType;
 import static android.view.WindowInsets.Type.systemBars;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
@@ -41,6 +43,7 @@
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowManager.LayoutParams;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -186,6 +189,32 @@
                         : systemBars());
     }
 
+    public Rect calculateVisibleInsets(Rect frame, Rect legacyVisibleInsets,
+            @SoftInputModeFlags int softInputMode) {
+        if (sNewInsetsMode == NEW_INSETS_MODE_NONE) {
+            return legacyVisibleInsets;
+        }
+
+        Insets insets = Insets.NONE;
+        for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+            InsetsSource source = mSources.get(type);
+            if (source == null) {
+                continue;
+            }
+            if (sNewInsetsMode != NEW_INSETS_MODE_FULL && type != ITYPE_IME) {
+                continue;
+            }
+
+            // Ignore everything that's not a system bar or IME.
+            int publicType = InsetsState.toPublicType(type);
+            if (!isVisibleInsetsType(publicType, softInputMode)) {
+                continue;
+            }
+            insets = Insets.max(source.calculateVisibleInsets(frame), insets);
+        }
+        return insets.toRect();
+    }
+
     private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
             Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray typeSideMap,
             @Nullable boolean[] typeVisibilityMap) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0db80e2..13d609b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -11117,7 +11117,21 @@
     }
 
     /**
-     * Dispatches {@link WindowInsetsAnimationCallback#onStarted(InsetsAnimation, AnimationBounds)}
+     * Dispatches {@link WindowInsetsAnimationCallback#onPrepare(InsetsAnimation)}
+     * when Window Insets animation is being prepared.
+     * @param animation current animation
+     *
+     * @see WindowInsetsAnimationCallback#onPrepare(InsetsAnimation)
+     */
+    public void dispatchWindowInsetsAnimationPrepare(
+            @NonNull InsetsAnimation animation) {
+        if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+            mListenerInfo.mWindowInsetsAnimationCallback.onPrepare(animation);
+        }
+    }
+
+    /**
+     * Dispatches {@link WindowInsetsAnimationCallback#onStart(InsetsAnimation, AnimationBounds)}
      * when Window Insets animation is started.
      * @param animation current animation
      * @param bounds the upper and lower {@link AnimationBounds} that provides range of
@@ -11125,10 +11139,10 @@
      * @return the upper and lower {@link AnimationBounds}.
      */
     @NonNull
-    public AnimationBounds dispatchWindowInsetsAnimationStarted(
+    public AnimationBounds dispatchWindowInsetsAnimationStart(
             @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
         if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
-            return mListenerInfo.mWindowInsetsAnimationCallback.onStarted(animation, bounds);
+            return mListenerInfo.mWindowInsetsAnimationCallback.onStart(animation, bounds);
         }
         return bounds;
     }
@@ -11149,13 +11163,13 @@
     }
 
     /**
-     * Dispatches {@link WindowInsetsAnimationCallback#onFinished(InsetsAnimation)}
+     * Dispatches {@link WindowInsetsAnimationCallback#onFinish(InsetsAnimation)}
      * when Window Insets animation finishes.
      * @param animation The current ongoing {@link InsetsAnimation}.
      */
-    public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
+    public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) {
         if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
-            mListenerInfo.mWindowInsetsAnimationCallback.onFinished(animation);
+            mListenerInfo.mWindowInsetsAnimationCallback.onFinish(animation);
         }
     }
 
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 5fb7177..047d7da 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -7199,13 +7199,23 @@
     }
 
     @Override
-    @NonNull
-    public AnimationBounds dispatchWindowInsetsAnimationStarted(
-            @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
-        super.dispatchWindowInsetsAnimationStarted(animation, bounds);
+    public void dispatchWindowInsetsAnimationPrepare(
+            @NonNull InsetsAnimation animation) {
+        super.dispatchWindowInsetsAnimationPrepare(animation);
         final int count = getChildCount();
         for (int i = 0; i < count; i++) {
-            getChildAt(i).dispatchWindowInsetsAnimationStarted(animation, bounds);
+            getChildAt(i).dispatchWindowInsetsAnimationPrepare(animation);
+        }
+    }
+
+    @Override
+    @NonNull
+    public AnimationBounds dispatchWindowInsetsAnimationStart(
+            @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
+        super.dispatchWindowInsetsAnimationStart(animation, bounds);
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            getChildAt(i).dispatchWindowInsetsAnimationStart(animation, bounds);
         }
         return bounds;
     }
@@ -7222,11 +7232,11 @@
     }
 
     @Override
-    public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
-        super.dispatchWindowInsetsAnimationFinished(animation);
+    public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) {
+        super.dispatchWindowInsetsAnimationFinish(animation);
         final int count = getChildCount();
         for (int i = 0; i < count; i++) {
-            getChildAt(i).dispatchWindowInsetsAnimationFinished(animation);
+            getChildAt(i).dispatchWindowInsetsAnimationFinish(animation);
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index bf8dc65..ca8ba4c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1464,6 +1464,7 @@
             return;
         }
         mApplyInsetsRequested = true;
+        requestLayout();
 
         // If this changes during traversal, no need to schedule another one as it will dispatch it
         // during the current traversal.
@@ -2102,6 +2103,12 @@
         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
 
+    private void updateVisibleInsets() {
+        Rect visibleInsets = mInsetsController.calculateVisibleInsets(mPendingVisibleInsets,
+                mWindowAttributes.softInputMode);
+        mAttachInfo.mVisibleInsets.set(visibleInsets);
+    }
+
     InsetsController getInsetsController() {
         return mInsetsController;
     }
@@ -2250,7 +2257,7 @@
                     insetsChanged = true;
                 }
                 if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
-                    mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+                    updateVisibleInsets();
                     if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
                             + mAttachInfo.mVisibleInsets);
                 }
@@ -2316,6 +2323,7 @@
 
         if (mApplyInsetsRequested) {
             mApplyInsetsRequested = false;
+            updateVisibleInsets();
             dispatchApplyInsets(host);
             if (mLayoutRequested) {
                 // Short-circuit catching a new layout request here, so
@@ -2500,7 +2508,7 @@
                     contentInsetsChanged = true;
                 }
                 if (visibleInsetsChanged) {
-                    mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+                    updateVisibleInsets();
                     if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
                             + mAttachInfo.mVisibleInsets);
                 }
diff --git a/core/java/android/view/WindowContainerTransaction.java b/core/java/android/view/WindowContainerTransaction.java
index 607a870..253794f 100644
--- a/core/java/android/view/WindowContainerTransaction.java
+++ b/core/java/android/view/WindowContainerTransaction.java
@@ -62,6 +62,18 @@
         return this;
     }
 
+    /**
+     * Notify activies within the hiearchy of a container that they have entered picture-in-picture
+     * mode with the given bounds.
+     */
+    public WindowContainerTransaction scheduleFinishEnterPip(IWindowContainer container,
+            Rect bounds) {
+        Change chg = getOrCreateChange(container.asBinder());
+        chg.mSchedulePipCallback = true;
+        chg.mPinnedBounds = new Rect(bounds);
+        return this;
+    }
+
     public Map<IBinder, Change> getChanges() {
         return mChanges;
     }
@@ -104,12 +116,20 @@
         private @ActivityInfo.Config int mConfigSetMask = 0;
         private @WindowConfiguration.WindowConfig int mWindowSetMask = 0;
 
+        private boolean mSchedulePipCallback = false;
+        private Rect mPinnedBounds = null;
+
         public Change() {}
 
         protected Change(Parcel in) {
             mConfiguration.readFromParcel(in);
             mConfigSetMask = in.readInt();
             mWindowSetMask = in.readInt();
+            mSchedulePipCallback = (in.readInt() != 0);
+            if (mSchedulePipCallback ) {
+                mPinnedBounds = new Rect();
+                mPinnedBounds.readFromParcel(in);
+            }
         }
 
         public Configuration getConfiguration() {
@@ -126,6 +146,14 @@
             return mWindowSetMask;
         }
 
+        /**
+         * Returns the bounds to be used for scheduling the enter pip callback
+         * or null if no callback is to be scheduled.
+         */
+        public Rect getEnterPipBounds() {
+            return mPinnedBounds;
+        }
+
         @Override
         public String toString() {
             final boolean changesBounds =
@@ -151,6 +179,11 @@
             mConfiguration.writeToParcel(dest, flags);
             dest.writeInt(mConfigSetMask);
             dest.writeInt(mWindowSetMask);
+
+            dest.writeInt(mSchedulePipCallback ? 1 : 0);
+            if (mSchedulePipCallback ) {
+                mPinnedBounds.writeToParcel(dest, flags);
+            }
         }
 
         @Override
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 9df131d..9291b56 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -29,6 +29,8 @@
 import static android.view.WindowInsets.Type.all;
 import static android.view.WindowInsets.Type.indexOf;
 import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -40,6 +42,7 @@
 import android.graphics.Rect;
 import android.util.SparseArray;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethod;
 
@@ -1289,6 +1292,17 @@
         public static @InsetsType int all() {
             return 0xFFFFFFFF;
         }
+
+        /**
+         * Checks whether the specified type is considered to be part of visible insets.
+         * @hide
+         */
+        public static boolean isVisibleInsetsType(int type,
+                @SoftInputModeFlags int softInputModeFlags) {
+            int softInputMode = softInputModeFlags & SOFT_INPUT_MASK_ADJUST;
+            return (type & Type.systemBars()) != 0
+                    || (softInputMode != SOFT_INPUT_ADJUST_NOTHING && (type & Type.ime()) != 0);
+        }
     }
 
     /**
diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java
index 5e71f27..e84c3e3 100644
--- a/core/java/android/view/WindowInsetsAnimationCallback.java
+++ b/core/java/android/view/WindowInsetsAnimationCallback.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Insets;
+import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.animation.Interpolator;
 
@@ -30,7 +31,47 @@
 public interface WindowInsetsAnimationCallback {
 
     /**
-     * Called when an inset animation gets started.
+     * Called when an insets animation is about to start and before the views have been laid out in
+     * the end state of the animation. The ordering of events during an insets animation is the
+     * following:
+     * <p>
+     * <ul>
+     *     <li>Application calls {@link WindowInsetsController#hideInputMethod()},
+     *     {@link WindowInsetsController#showInputMethod()},
+     *     {@link WindowInsetsController#controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)}</li>
+     *     <li>onPrepare is called on the view hierarchy listeners</li>
+     *     <li>{@link View#onApplyWindowInsets} will be called with the end state of the
+     *     animation</li>
+     *     <li>View hierarchy gets laid out according to the changes the application has requested
+     *     due to the new insets being dispatched</li>
+     *     <li>{@link #onStart} is called <em>before</em> the view
+     *     hierarchy gets drawn in the new laid out state</li>
+     *     <li>{@link #onProgress} is called immediately after with the animation start state</li>
+     *     <li>The frame gets drawn.</li>
+     * </ul>
+     * <p>
+     * This ordering allows the application to inspect the end state after the animation has
+     * finished, and then revert to the starting state of the animation in the first
+     * {@link #onProgress} callback by using post-layout view properties like {@link View#setX} and
+     * related methods.
+     * <p>
+     * Note: If the animation is application controlled by using
+     * {@link WindowInsetsController#controlInputMethodAnimation}, the end state of the animation
+     * is undefined as the application may decide on the end state only by passing in the
+     * {@code shown} parameter when calling {@link WindowInsetsAnimationController#finish}. In this
+     * situation, the system will dispatch the insets in the opposite visibility state before the
+     * animation starts. Example: When controlling the input method with
+     * {@link WindowInsetsController#controlInputMethodAnimation} and the input method is currently
+     * showing, {@link View#onApplyWindowInsets} will receive a {@link WindowInsets} instance for
+     * which {@link WindowInsets#isVisible} will return {@code false} for {@link Type#ime}.
+     *
+     * @param animation The animation that is about to start.
+     */
+    default void onPrepare(@NonNull InsetsAnimation animation) {
+    }
+
+    /**
+     * Called when an insets animation gets started.
      * <p>
      * Note that, like {@link #onProgress}, dispatch of the animation start event is hierarchical:
      * It will starts at the root of the view hierarchy and then traverse it and invoke the callback
@@ -45,7 +86,7 @@
      *         subtree of the hierarchy.
      */
     @NonNull
-    default AnimationBounds onStarted(
+    default AnimationBounds onStart(
             @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
         return bounds;
     }
@@ -72,12 +113,12 @@
     WindowInsets onProgress(@NonNull WindowInsets insets);
 
     /**
-     * Called when an inset animation has finished.
+     * Called when an insets animation has finished.
      *
      * @param animation The animation that has finished running. This will be the same instance as
-     *                  passed into {@link #onStarted}
+     *                  passed into {@link #onStart}
      */
-    default void onFinished(@NonNull InsetsAnimation animation) {
+    default void onFinish(@NonNull InsetsAnimation animation) {
     }
 
     /**
@@ -253,14 +294,14 @@
 
         /**
          * Insets both the lower and upper bound by the specified insets. This is to be used in
-         * {@link WindowInsetsAnimationCallback#onStarted} to indicate that a part of the insets has
+         * {@link WindowInsetsAnimationCallback#onStart} to indicate that a part of the insets has
          * been used to offset or clip its children, and the children shouldn't worry about that
          * part anymore.
          *
          * @param insets The amount to inset.
          * @return A copy of this instance inset in the given directions.
          * @see WindowInsets#inset
-         * @see WindowInsetsAnimationCallback#onStarted
+         * @see WindowInsetsAnimationCallback#onStart
          */
         @NonNull
         public AnimationBounds inset(@NonNull Insets insets) {
diff --git a/core/java/android/view/WindowInsetsAnimationControlListener.java b/core/java/android/view/WindowInsetsAnimationControlListener.java
index 8a226c1..f91254d 100644
--- a/core/java/android/view/WindowInsetsAnimationControlListener.java
+++ b/core/java/android/view/WindowInsetsAnimationControlListener.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.Hide;
 import android.annotation.NonNull;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.inputmethod.EditorInfo;
@@ -26,6 +27,12 @@
 public interface WindowInsetsAnimationControlListener {
 
     /**
+     * @hide
+     */
+    default void onPrepare(int types) {
+    }
+
+    /**
      * Called when the animation is ready to be controlled. This may be delayed when the IME needs
      * to redraw because of an {@link EditorInfo} change, or when the window is starting up.
      *
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index 6de56be..9d7f292 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -149,7 +149,8 @@
      *
      * @param types The {@link InsetsType}s the application has requested to control.
      * @param durationMillis duration of animation in
-     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}
+     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the
+     *                       animation doesn't have a predetermined duration.
      * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
      *                 windows are ready to be controlled, among other callbacks.
      * @hide
@@ -162,7 +163,8 @@
      * modifying the position of the IME when it's causing insets.
      *
      * @param durationMillis duration of the animation in
-     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}
+     *                       {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the
+     *                       animation doesn't have a predetermined duration.
      * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
      *                 IME are ready to be controlled, among other callbacks.
      */
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index a0cf534..20af76b 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -5921,8 +5921,6 @@
         // The offsets of that last touch down event. Remembered to start selection there.
         private int mMinTouchOffset, mMaxTouchOffset;
 
-        private boolean mGestureStayedInTapRegion;
-
         // Where the user first starts the drag motion.
         private int mStartOffset = -1;
 
@@ -6029,8 +6027,7 @@
                                 eventX, eventY);
 
                         // Double tap detection
-                        if (mGestureStayedInTapRegion
-                                && mTouchState.isMultiTapInSameArea()
+                        if (mTouchState.isMultiTapInSameArea()
                                 && (isMouse || isPositionOnText(eventX, eventY))) {
                             if (TextView.DEBUG_CURSOR) {
                                 logCursor("SelectionModifierCursorController: onTouchEvent",
@@ -6043,7 +6040,6 @@
                             }
                             mDiscardNextActionUp = true;
                         }
-                        mGestureStayedInTapRegion = true;
                         mHaventMovedEnoughToStartDrag = true;
                     }
                     break;
@@ -6059,25 +6055,8 @@
                     break;
 
                 case MotionEvent.ACTION_MOVE:
-                    final ViewConfiguration viewConfig = ViewConfiguration.get(
-                            mTextView.getContext());
-
-                    if (mGestureStayedInTapRegion || mHaventMovedEnoughToStartDrag) {
-                        final float deltaX = eventX - mTouchState.getLastDownX();
-                        final float deltaY = eventY - mTouchState.getLastDownY();
-                        final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
-
-                        if (mGestureStayedInTapRegion) {
-                            int doubleTapTouchSlop = viewConfig.getScaledDoubleTapTouchSlop();
-                            mGestureStayedInTapRegion =
-                                    distanceSquared <= doubleTapTouchSlop * doubleTapTouchSlop;
-                        }
-                        if (mHaventMovedEnoughToStartDrag) {
-                            // We don't start dragging until the user has moved enough.
-                            int touchSlop = viewConfig.getScaledTouchSlop();
-                            mHaventMovedEnoughToStartDrag =
-                                    distanceSquared <= touchSlop * touchSlop;
-                        }
+                    if (mHaventMovedEnoughToStartDrag) {
+                        mHaventMovedEnoughToStartDrag = !mTouchState.isMovedEnoughForDrag();
                     }
 
                     if (isMouse && !isDragAcceleratorActive()) {
diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java
index d53099d..6277afe 100644
--- a/core/java/android/widget/EditorTouchState.java
+++ b/core/java/android/widget/EditorTouchState.java
@@ -31,13 +31,15 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Helper class used by {@link Editor} to track state for touch events.
+ * Helper class used by {@link Editor} to track state for touch events. Ideally the logic here
+ * should be replaced with {@link android.view.GestureDetector}.
  *
  * @hide
  */
 @VisibleForTesting(visibility = PACKAGE)
 public class EditorTouchState {
     private float mLastDownX, mLastDownY;
+    private long mLastDownMillis;
     private float mLastUpX, mLastUpY;
     private long mLastUpMillis;
 
@@ -106,9 +108,18 @@
         final int action = event.getActionMasked();
         if (action == MotionEvent.ACTION_DOWN) {
             final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
+
+            // We check both the time between the last up and current down event, as well as the
+            // time between the first down and up events. The latter check is necessary to handle
+            // the case when the user taps, drags/holds for some time, and then lifts up and
+            // quickly taps in the same area. This scenario should not be treated as a double-tap.
+            // This follows the behavior in GestureDetector.
             final long millisSinceLastUp = event.getEventTime() - mLastUpMillis;
+            final long millisBetweenLastDownAndLastUp = mLastUpMillis - mLastDownMillis;
+
             // Detect double tap and triple click.
             if (millisSinceLastUp <= ViewConfiguration.getDoubleTapTimeout()
+                    && millisBetweenLastDownAndLastUp <= ViewConfiguration.getDoubleTapTimeout()
                     && (mMultiTapStatus == MultiTapStatus.FIRST_TAP
                     || (mMultiTapStatus == MultiTapStatus.DOUBLE_TAP && isMouse))) {
                 if (mMultiTapStatus == MultiTapStatus.FIRST_TAP) {
@@ -133,6 +144,7 @@
             }
             mLastDownX = event.getX();
             mLastDownY = event.getY();
+            mLastDownMillis = event.getEventTime();
             mMovedEnoughForDrag = false;
             mIsDragCloseToVertical = false;
         } else if (action == MotionEvent.ACTION_UP) {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index d86766e..01a0e9b 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -565,7 +565,8 @@
     }
 
     private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) {
-        if (icon != null && icon.getType() == Icon.TYPE_URI) {
+        if (icon != null && (icon.getType() == Icon.TYPE_URI
+                || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
             visitor.accept(icon.getUri());
         }
     }
diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
index 0a0d05c..de204ba 100644
--- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -16,6 +16,7 @@
 package com.android.internal.app;
 
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 import static android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
@@ -24,13 +25,17 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -49,7 +54,10 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
 
 /**
  * Activity used to display and persist a service or feature target for the Accessibility button.
@@ -59,12 +67,46 @@
     private static final String MAGNIFICATION_COMPONENT_ID =
             "com.android.server.accessibility.MagnificationController";
 
+    private static final char SERVICES_SEPARATOR = ':';
+    private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
+            new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
+    private static final int ACCESSIBILITY_BUTTON_USER_TYPE = convertToUserType(
+            ACCESSIBILITY_BUTTON);
+    private static final int ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE = convertToUserType(
+            ACCESSIBILITY_SHORTCUT_KEY);
+
     private int mShortcutType;
     private List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
     private AlertDialog mAlertDialog;
     private TargetAdapter mTargetAdapter;
 
     /**
+     * Annotation for different user shortcut type UI type.
+     *
+     * {@code DEFAULT} for displaying default value.
+     * {@code SOFTWARE} for displaying specifying the accessibility services or features which
+     * choose accessibility button in the navigation bar as preferred shortcut.
+     * {@code HARDWARE} for displaying specifying the accessibility services or features which
+     * choose accessibility shortcut as preferred shortcut.
+     * {@code TRIPLETAP} for displaying specifying magnification to be toggled via quickly
+     * tapping screen 3 times as preferred shortcut.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            UserShortcutType.DEFAULT,
+            UserShortcutType.SOFTWARE,
+            UserShortcutType.HARDWARE,
+            UserShortcutType.TRIPLETAP,
+    })
+    /** Denotes the user shortcut type. */
+    public @interface UserShortcutType {
+        int DEFAULT = 0;
+        int SOFTWARE = 1; // 1 << 0
+        int HARDWARE = 2; // 1 << 1
+        int TRIPLETAP = 4; // 1 << 2
+    }
+
+    /**
      * Annotation for different accessibilityService fragment UI type.
      *
      * {@code LEGACY} for displaying appearance aligned with sdk version Q accessibility service
@@ -395,19 +437,63 @@
     }
 
     private void onTargetDeleted(AdapterView<?> parent, View view, int position, long id) {
-        // TODO(b/146967898): disable service when deleting the target and the target only have
-        //  last one shortcut item, only remove it from shortcut list otherwise.
-        if ((mShortcutType == ACCESSIBILITY_BUTTON) && (mTargets.get(position).mFragmentType
-                != AccessibilityServiceFragmentType.LEGACY)) {
-            mTargets.remove(position);
-            mTargetAdapter.notifyDataSetChanged();
+        final AccessibilityButtonTarget target = mTargets.get(position);
+        final ComponentName targetComponentName =
+                ComponentName.unflattenFromString(target.getId());
+
+        switch (target.getFragmentType()) {
+            case AccessibilityServiceFragmentType.INVISIBLE:
+                onInvisibleTargetDeleted(targetComponentName);
+                break;
+            case AccessibilityServiceFragmentType.INTUITIVE:
+                onIntuitiveTargetDeleted(targetComponentName);
+                break;
+            case AccessibilityServiceFragmentType.LEGACY:
+            case AccessibilityServiceFragmentType.BOUNCE:
+                // Do nothing
+                break;
+            default:
+                throw new IllegalStateException("Unexpected fragment type");
         }
 
+        mTargets.remove(position);
+        mTargetAdapter.notifyDataSetChanged();
+
         if (mTargets.isEmpty()) {
             mAlertDialog.dismiss();
         }
     }
 
+    private void onInvisibleTargetDeleted(ComponentName componentName) {
+        if (mShortcutType == ACCESSIBILITY_BUTTON) {
+            optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName);
+
+            if (!hasValueInSettings(this,
+                    ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName)) {
+                setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+            }
+        } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+            optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName);
+
+            if (!hasValueInSettings(this,
+                    ACCESSIBILITY_BUTTON_USER_TYPE, componentName)) {
+                setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+            }
+        } else {
+            throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
+        }
+    }
+
+    private void onIntuitiveTargetDeleted(ComponentName componentName) {
+        if (mShortcutType == ACCESSIBILITY_BUTTON) {
+            optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName);
+        } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+            optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName);
+        } else {
+            throw new IllegalArgumentException("Unsupported shortcut type:" + mShortcutType);
+        }
+    }
+
     private void onCancelButtonClicked() {
         mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH);
         mTargetAdapter.notifyDataSetChanged();
@@ -437,4 +523,166 @@
         mAlertDialog.getListView().setOnItemClickListener(
                 isEditMenuMode ? this::onTargetDeleted : this::onTargetSelected);
     }
+
+    /**
+     * @return the set of enabled accessibility services for {@param userId}. If there are no
+     * services, it returns the unmodifiable {@link Collections#emptySet()}.
+     */
+    private Set<ComponentName> getEnabledServicesFromSettings(Context context, int userId) {
+        final String enabledServicesSetting = Settings.Secure.getStringForUser(
+                context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                userId);
+        if (TextUtils.isEmpty(enabledServicesSetting)) {
+            return Collections.emptySet();
+        }
+
+        final Set<ComponentName> enabledServices = new HashSet<>();
+        final TextUtils.StringSplitter colonSplitter =
+                new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
+        colonSplitter.setString(enabledServicesSetting);
+
+        for (String componentNameString : colonSplitter) {
+            final ComponentName enabledService = ComponentName.unflattenFromString(
+                    componentNameString);
+            if (enabledService != null) {
+                enabledServices.add(enabledService);
+            }
+        }
+
+        return enabledServices;
+    }
+
+    /**
+     * Changes an accessibility component's state.
+     */
+    private void setAccessibilityServiceState(Context context, ComponentName componentName,
+            boolean enabled) {
+        setAccessibilityServiceState(context, componentName, enabled, UserHandle.myUserId());
+    }
+
+    /**
+     * Changes an accessibility component's state for {@param userId}.
+     */
+    private void setAccessibilityServiceState(Context context, ComponentName componentName,
+            boolean enabled, int userId) {
+        Set<ComponentName> enabledServices = getEnabledServicesFromSettings(
+                context, userId);
+
+        if (enabledServices.isEmpty()) {
+            enabledServices = new ArraySet<>(/* capacity= */ 1);
+        }
+
+        if (enabled) {
+            enabledServices.add(componentName);
+        } else {
+            enabledServices.remove(componentName);
+        }
+
+        final StringBuilder enabledServicesBuilder = new StringBuilder();
+        for (ComponentName enabledService : enabledServices) {
+            enabledServicesBuilder.append(enabledService.flattenToString());
+            enabledServicesBuilder.append(
+                    SERVICES_SEPARATOR);
+        }
+
+        final int enabledServicesBuilderLength = enabledServicesBuilder.length();
+        if (enabledServicesBuilderLength > 0) {
+            enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
+        }
+
+        Settings.Secure.putStringForUser(context.getContentResolver(),
+                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                enabledServicesBuilder.toString(), userId);
+    }
+
+    /**
+     * Opts out component name into colon-separated {@code shortcutType} key's string in Settings.
+     *
+     * @param context The current context.
+     * @param shortcutType The preferred shortcut type user selected.
+     * @param componentName The component name that need to be opted out from Settings.
+     */
+    private void optOutValueFromSettings(
+            Context context, int shortcutType, ComponentName componentName) {
+        final StringJoiner joiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
+        final String targetsKey = convertToKey(shortcutType);
+        final String targetsValue = Settings.Secure.getString(context.getContentResolver(),
+                targetsKey);
+
+        if (TextUtils.isEmpty(targetsValue)) {
+            return;
+        }
+
+        sStringColonSplitter.setString(targetsValue);
+        while (sStringColonSplitter.hasNext()) {
+            final String name = sStringColonSplitter.next();
+            if (TextUtils.isEmpty(name) || (componentName.flattenToString()).equals(name)) {
+                continue;
+            }
+            joiner.add(name);
+        }
+
+        Settings.Secure.putString(context.getContentResolver(), targetsKey, joiner.toString());
+    }
+
+    /**
+     * Returns if component name existed in Settings.
+     *
+     * @param context The current context.
+     * @param shortcutType The preferred shortcut type user selected.
+     * @param componentName The component name that need to be checked existed in Settings.
+     * @return {@code true} if componentName existed in Settings.
+     */
+    private boolean hasValueInSettings(Context context, @UserShortcutType int shortcutType,
+            @NonNull ComponentName componentName) {
+        final String targetKey = convertToKey(shortcutType);
+        final String targetString = Settings.Secure.getString(context.getContentResolver(),
+                targetKey);
+
+        if (TextUtils.isEmpty(targetString)) {
+            return false;
+        }
+
+        sStringColonSplitter.setString(targetString);
+        while (sStringColonSplitter.hasNext()) {
+            final String name = sStringColonSplitter.next();
+            if ((componentName.flattenToString()).equals(name)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Converts {@link UserShortcutType} to key in Settings.
+     *
+     * @param type The shortcut type.
+     * @return Mapping key in Settings.
+     */
+    private String convertToKey(@UserShortcutType int type) {
+        switch (type) {
+            case UserShortcutType.SOFTWARE:
+                return Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT;
+            case UserShortcutType.HARDWARE:
+                return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
+            case UserShortcutType.TRIPLETAP:
+                return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
+            default:
+                throw new IllegalArgumentException(
+                        "Unsupported user shortcut type: " + type);
+        }
+    }
+
+    private static @UserShortcutType int convertToUserType(@ShortcutType int type) {
+        switch (type) {
+            case ACCESSIBILITY_BUTTON:
+                return UserShortcutType.SOFTWARE;
+            case ACCESSIBILITY_SHORTCUT_KEY:
+                return UserShortcutType.HARDWARE;
+            default:
+                throw new IllegalArgumentException(
+                        "Unsupported shortcut type:" + type);
+        }
+    }
 }
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index d94294f..74bfb96 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -61,10 +61,6 @@
     int setParameter(in ParcelUuid soundModelId, in ModelParams modelParam,
         int value);
 
-    /**
-     * @throws UnsupportedOperationException if hal or model do not support this API.
-     * @throws IllegalArgumentException if invalid model handle or parameter is passed.
-     */
     int getParameter(in ParcelUuid soundModelId, in ModelParams modelParam);
 
     @nullable SoundTrigger.ModelParamRange queryParameter(in ParcelUuid soundModelId,
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index f462f5d..be2d1d6 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -26,6 +26,7 @@
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.IVoiceInteractionSessionListener;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.service.voice.IVoiceInteractionService;
 import android.service.voice.IVoiceInteractionSession;
@@ -91,6 +92,49 @@
      */
     int stopRecognition(in IVoiceInteractionService service, int keyphraseId,
             in IRecognitionStatusCallback callback);
+    /**
+     * Set a model specific ModelParams with the given value. This
+     * parameter will keep its value for the duration the model is loaded regardless of starting and
+     * stopping recognition. Once the model is unloaded, the value will be lost.
+     * queryParameter should be checked first before calling this method.
+     *
+     * @param service The current VoiceInteractionService.
+     * @param keyphraseId The unique identifier for the keyphrase.
+     * @param modelParam   ModelParams
+     * @param value        Value to set
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+     *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+     *           if API is not supported by HAL
+     */
+    int setParameter(in IVoiceInteractionService service, int keyphraseId,
+            in ModelParams modelParam, int value);
+    /**
+     * Get a model specific ModelParams. This parameter will keep its value
+     * for the duration the model is loaded regardless of starting and stopping recognition.
+     * Once the model is unloaded, the value will be lost. If the value is not set, a default
+     * value is returned. See ModelParams for parameter default values.
+     * queryParameter should be checked first before calling this method.
+     *
+     * @param service The current VoiceInteractionService.
+     * @param keyphraseId The unique identifier for the keyphrase.
+     * @param modelParam   ModelParams
+     * @return value of parameter
+     */
+    int getParameter(in IVoiceInteractionService service, int keyphraseId,
+            in ModelParams modelParam);
+    /**
+     * Determine if parameter control is supported for the given model handle.
+     * This method should be checked prior to calling setParameter or getParameter.
+     *
+     * @param service The current VoiceInteractionService.
+     * @param keyphraseId The unique identifier for the keyphrase.
+     * @param modelParam ModelParams
+     * @return supported range of parameter, null if not supported
+     */
+    @nullable SoundTrigger.ModelParamRange queryParameter(in IVoiceInteractionService service,
+            int keyphraseId, in ModelParams modelParam);
 
     /**
      * @return the component name for the currently active voice interaction service
diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java
index cca97f6..3a450de 100644
--- a/core/java/com/android/internal/logging/UiEventLogger.java
+++ b/core/java/com/android/internal/logging/UiEventLogger.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.logging;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 /**
  * Logging interface for UI events. Normal implementation is UiEventLoggerImpl.
  * For testing, use fake implementation UiEventLoggerFake.
@@ -26,13 +29,24 @@
     /** Put your Event IDs in enums that implement this interface, and document them using the
      * UiEventEnum annotation.
      * Event IDs must be globally unique. This will be enforced by tooling (forthcoming).
-     * OEMs should use event IDs above 100000.
+     * OEMs should use event IDs above 100000 and below 1000000 (1 million).
      */
     interface UiEventEnum {
         int getId();
     }
+
     /**
-     * Log a simple event, with no package or instance ID.
+     * Log a simple event, with no package information. Does nothing if event.getId() <= 0.
+     * @param event an enum implementing UiEventEnum interface.
      */
-    void log(UiEventEnum eventID);
+    void log(@NonNull UiEventEnum event);
+
+    /**
+     * Log an event with package information. Does nothing if event.getId() <= 0.
+     * Give both uid and packageName if both are known, but one may be omitted if unknown.
+     * @param event an enum implementing UiEventEnum interface.
+     * @param uid the uid of the relevant app, if known (0 otherwise).
+     * @param packageName the package name of the relevant app, if known (null otherwise).
+     */
+    void log(@NonNull UiEventEnum event, int uid, @Nullable String packageName);
 }
diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
index e64fba2..bdf460c 100644
--- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java
+++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
@@ -24,14 +24,16 @@
  * See UiEventReported atom in atoms.proto for more context.
  */
 public class UiEventLoggerImpl implements UiEventLogger {
-    /**
-     * Log a simple event, with no package or instance ID.
-     */
     @Override
     public void log(UiEventEnum event) {
+        log(event, 0, null);
+    }
+
+    @Override
+    public void log(UiEventEnum event, int uid, String packageName) {
         final int eventID = event.getId();
         if (eventID > 0) {
-            StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, 0, null);
+            StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, uid, packageName);
         }
     }
 }
diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
index 92e9bbb..6be5b81 100644
--- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
+++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
@@ -30,7 +30,7 @@
     /**
      * Immutable data class used to record fake log events.
      */
-    public class FakeUiEvent {
+    public static class FakeUiEvent {
         public final int eventId;
         public final int uid;
         public final String packageName;
@@ -44,15 +44,20 @@
 
     private Queue<FakeUiEvent> mLogs = new LinkedList<FakeUiEvent>();
 
-    @Override
-    public void log(UiEventEnum event) {
-        final int eventId = event.getId();
-        if (eventId > 0) {
-            mLogs.offer(new FakeUiEvent(eventId, 0, null));
-        }
-    }
-
     public Queue<FakeUiEvent> getLogs() {
         return mLogs;
     }
+
+    @Override
+    public void log(UiEventEnum event) {
+        log(event, 0, null);
+    }
+
+    @Override
+    public void log(UiEventEnum event, int uid, String packageName) {
+        final int eventId = event.getId();
+        if (eventId > 0) {
+            mLogs.offer(new FakeUiEvent(eventId, uid, packageName));
+        }
+    }
 }
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index 5bc96d8..408a7a8 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -126,7 +126,9 @@
      * @param reference an object reference
      * @return the non-null reference that was validated
      * @throws NullPointerException if {@code reference} is null
+     * @deprecated - use {@link java.util.Objects.requireNonNull} instead.
      */
+    @Deprecated
     @UnsupportedAppUsage
     public static @NonNull <T> T checkNotNull(final T reference) {
         if (reference == null) {
@@ -144,7 +146,9 @@
      *     be converted to a string using {@link String#valueOf(Object)}
      * @return the non-null reference that was validated
      * @throws NullPointerException if {@code reference} is null
+     * @deprecated - use {@link java.util.Objects.requireNonNull} instead.
      */
+    @Deprecated
     @UnsupportedAppUsage
     public static @NonNull <T> T checkNotNull(final T reference, final Object errorMessage) {
         if (reference == null) {
@@ -154,26 +158,6 @@
     }
 
     /**
-     * Ensures that an object reference passed as a parameter to the calling
-     * method is not null.
-     *
-     * @param reference an object reference
-     * @param messageTemplate a printf-style message template to use if the check fails; will
-     *     be converted to a string using {@link String#format(String, Object...)}
-     * @param messageArgs arguments for {@code messageTemplate}
-     * @return the non-null reference that was validated
-     * @throws NullPointerException if {@code reference} is null
-     */
-    public static @NonNull <T> T checkNotNull(final T reference,
-            final String messageTemplate,
-            final Object... messageArgs) {
-        if (reference == null) {
-            throw new NullPointerException(String.format(messageTemplate, messageArgs));
-        }
-        return reference;
-    }
-
-    /**
      * Ensures the truth of an expression involving the state of the calling
      * instance, but not involving any parameters to the calling method.
      *
diff --git a/core/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/RouteDiscoveryRequest.aidl b/core/jni/android/graphics/MimeType.h
similarity index 85%
copy from media/java/android/media/RouteDiscoveryRequest.aidl
copy to core/jni/android/graphics/MimeType.h
index 744f656..38a579c 100644
--- a/media/java/android/media/RouteDiscoveryRequest.aidl
+++ b/core/jni/android/graphics/MimeType.h
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
-package android.media;
+#pragma once
 
-parcelable RouteDiscoveryRequest;
+#include "SkEncodedImageFormat.h"
+
+const char* getMimeType(SkEncodedImageFormat);
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 79cf019..53327bc 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2312,6 +2312,48 @@
     return jStatus;
 }
 
+static jint
+android_media_AudioSystem_getDevicesForAttributes(JNIEnv *env, jobject thiz,
+        jobject jaa, jobjectArray jDeviceArray)
+{
+    const jsize maxResultSize = env->GetArrayLength(jDeviceArray);
+    // the JNI is always expected to provide us with an array capable of holding enough
+    // devices i.e. the most we ever route a track to. This is preferred over receiving an ArrayList
+    // with reverse JNI to make the array grow as need as this would be less efficient, and some
+    // components call this method often
+    if (jDeviceArray == nullptr || maxResultSize == 0) {
+        ALOGE("%s invalid array to store AudioDeviceAddress", __FUNCTION__);
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+
+    JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
+    jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
+    if (jStatus != (jint) AUDIO_JAVA_SUCCESS) {
+        return jStatus;
+    }
+
+    AudioDeviceTypeAddrVector devices;
+    jStatus = check_AudioSystem_Command(
+            AudioSystem::getDevicesForAttributes(*(paa.get()), &devices));
+    if (jStatus != NO_ERROR) {
+        return jStatus;
+    }
+
+    if (devices.size() > maxResultSize) {
+        return AUDIO_JAVA_INVALID_OPERATION;
+    }
+    size_t index = 0;
+    jobject jAudioDeviceAddress = NULL;
+    for (const auto& device : devices) {
+        jStatus = createAudioDeviceAddressFromNative(env, &jAudioDeviceAddress, &device);
+        if (jStatus != AUDIO_JAVA_SUCCESS) {
+            return jStatus;
+        }
+        env->SetObjectArrayElement(jDeviceArray, index++, jAudioDeviceAddress);
+    }
+    return jStatus;
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gMethods[] = {
@@ -2395,6 +2437,7 @@
     {"setPreferredDeviceForStrategy", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setPreferredDeviceForStrategy},
     {"removePreferredDeviceForStrategy", "(I)I", (void *)android_media_AudioSystem_removePreferredDeviceForStrategy},
     {"getPreferredDeviceForStrategy", "(I[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getPreferredDeviceForStrategy},
+    {"getDevicesForAttributes", "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getDevicesForAttributes}
 };
 
 static const JNINativeMethod gEventHandlerMethods[] = {
diff --git a/core/jni/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/server/animationadapter.proto b/core/proto/android/server/animationadapter.proto
index c6925f4..70627ed 100644
--- a/core/proto/android/server/animationadapter.proto
+++ b/core/proto/android/server/animationadapter.proto
@@ -50,7 +50,6 @@
     optional WindowAnimationSpecProto window = 1;
     optional MoveAnimationSpecProto move = 2;
     optional AlphaAnimationSpecProto alpha = 3;
-    optional RotationAnimationSpecProto rotate = 4;
 }
 
 /* represents WindowAnimationSpec */
@@ -77,12 +76,3 @@
     optional float to = 2;
     optional int64 duration_ms = 3;
 }
-
-/* represents RotationAnimationSpec */
-message RotationAnimationSpecProto {
-    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
-
-    optional float start_luma = 1;
-    optional float end_luma = 2;
-    optional int64 duration_ms = 3;
-}
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
index 557075c..2de5b7f 100644
--- a/core/proto/android/service/graphicsstats.proto
+++ b/core/proto/android/service/graphicsstats.proto
@@ -32,6 +32,10 @@
 }
 
 message GraphicsStatsProto {
+    enum PipelineType {
+        GL = 0;
+        VULKAN = 1;
+    }
     option (android.msg_privacy).dest = DEST_AUTOMATIC;
 
     // The package name of the app
@@ -54,6 +58,9 @@
 
     // The gpu frame time histogram for the package
     repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7;
+
+    // HWUI renders pipeline type: GL or Vulkan
+    optional PipelineType pipeline = 8;
 }
 
 message GraphicsStatsJankSummaryProto {
diff --git a/core/proto/android/stats/launcher/Android.bp b/core/proto/android/stats/launcher/Android.bp
index b8fb6ff..976a0b8 100644
--- a/core/proto/android/stats/launcher/Android.bp
+++ b/core/proto/android/stats/launcher/Android.bp
@@ -25,3 +25,16 @@
         "*.proto",
     ],
 }
+
+java_library {
+    name: "launcherprotoslite",
+    proto: {
+        type: "lite",
+        include_dirs: ["external/protobuf/src"],
+    },
+
+    sdk_version: "current",
+    srcs: [
+        "*.proto",
+    ],
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5aa0a2d..0e4cea1 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -370,6 +370,7 @@
     <protected-broadcast android:name="android.net.wifi.STATE_CHANGE" />
     <protected-broadcast android:name="android.net.wifi.LINK_CONFIGURATION_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.CONFIGURED_NETWORKS_CHANGE" />
+    <protected-broadcast android:name="android.net.wifi.action.NETWORK_SETTINGS_RESET" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_ICON" />
     <protected-broadcast android:name="android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST" />
@@ -639,10 +640,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" />
@@ -1654,6 +1651,7 @@
     <!-- Allows holder to request bluetooth/wifi scan bypassing global "use location" setting and
          location permissions.
          <p>Not for use by third-party or privileged applications.
+         @SystemApi
          @hide
     -->
     <permission android:name="android.permission.RADIO_SCAN_WITHOUT_LOCATION"
@@ -1789,6 +1787,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
@@ -2069,8 +2072,10 @@
         android:protectionLevel="signature|privileged" />
 
     <!-- Allows read only access to precise phone state.
-         @hide Pending API council approval -->
+         Allows reading of detailed information about phone state for special-use applications
+         such as dialers, carrier applications, or ims applications. -->
     <permission android:name="android.permission.READ_PRECISE_PHONE_STATE"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:protectionLevel="signature|privileged" />
 
     <!-- @SystemApi Allows read access to privileged phone state.
diff --git a/core/res/res/anim/screen_rotate_0_enter.xml b/core/res/res/anim/screen_rotate_0_enter.xml
index 629be7e..93cf365 100644
--- a/core/res/res/anim/screen_rotate_0_enter.xml
+++ b/core/res/res/anim/screen_rotate_0_enter.xml
@@ -1,25 +1,25 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
-    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
-        android:interpolator="@interpolator/screen_rotation_alpha_in"
-        android:fillEnabled="true"
-        android:fillBefore="true" android:fillAfter="true"
-        android:duration="@android:integer/config_screen_rotation_fade_in" />
+        android:shareInterpolator="false">
+    <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:duration="@android:integer/config_shortAnimTime" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_0_exit.xml b/core/res/res/anim/screen_rotate_0_exit.xml
index fa046a0..37d5a411 100644
--- a/core/res/res/anim/screen_rotate_0_exit.xml
+++ b/core/res/res/anim/screen_rotate_0_exit.xml
@@ -1,25 +1,22 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
+/*
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
-    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
-        android:interpolator="@interpolator/screen_rotation_alpha_out"
-        android:fillEnabled="true"
-        android:fillBefore="true" android:fillAfter="true"
-        android:duration="@android:integer/config_screen_rotation_fade_out" />
+        android:shareInterpolator="false">
 </set>
diff --git a/core/res/res/anim/screen_rotate_0_frame.xml b/core/res/res/anim/screen_rotate_0_frame.xml
new file mode 100644
index 0000000..5ea9bf8
--- /dev/null
+++ b/core/res/res/anim/screen_rotate_0_frame.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+        android:shareInterpolator="false">
+    <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:duration="@android:integer/config_shortAnimTime" />
+</set>
diff --git a/core/res/res/anim/screen_rotate_180_enter.xml b/core/res/res/anim/screen_rotate_180_enter.xml
index 889a615..688a8d5 100644
--- a/core/res/res/anim/screen_rotate_180_enter.xml
+++ b/core/res/res/anim/screen_rotate_180_enter.xml
@@ -18,11 +18,11 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
+        android:shareInterpolator="false">
     <rotate android:fromDegrees="180" android:toDegrees="0"
-        android:pivotX="50%" android:pivotY="50%"
-        android:fillEnabled="true"
-        android:fillBefore="true" android:fillAfter="true"
-        android:interpolator="@interpolator/fast_out_slow_in"
-        android:duration="@android:integer/config_screen_rotation_total_180" />
+            android:pivotX="50%" android:pivotY="50%"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_mediumAnimTime" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_180_exit.xml b/core/res/res/anim/screen_rotate_180_exit.xml
index 766fcfa..58a1868 100644
--- a/core/res/res/anim/screen_rotate_180_exit.xml
+++ b/core/res/res/anim/screen_rotate_180_exit.xml
@@ -18,11 +18,11 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
+        android:shareInterpolator="false">
     <rotate android:fromDegrees="0" android:toDegrees="-180"
-        android:pivotX="50%" android:pivotY="50%"
-        android:fillEnabled="true"
-        android:fillBefore="true" android:fillAfter="true"
-        android:interpolator="@interpolator/fast_out_slow_in"
-        android:duration="@android:integer/config_screen_rotation_total_180" />
+            android:pivotX="50%" android:pivotY="50%"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_mediumAnimTime" />
 </set>
\ No newline at end of file
diff --git a/core/res/res/anim/screen_rotate_alpha.xml b/core/res/res/anim/screen_rotate_alpha.xml
index 2cac982..c49ef9c 100644
--- a/core/res/res/anim/screen_rotate_alpha.xml
+++ b/core/res/res/anim/screen_rotate_alpha.xml
@@ -20,8 +20,8 @@
 <set xmlns:android="http://schemas.android.com/apk/res/android"
         android:shareInterpolator="false">
     <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
-           android:interpolator="@interpolator/screen_rotation_alpha_out"
+           android:interpolator="@interpolator/decelerate_quint"
            android:fillEnabled="true"
            android:fillBefore="true" android:fillAfter="true"
-           android:duration="@android:integer/config_screen_rotation_fade_out" />
+           android:duration="@android:integer/config_mediumAnimTime" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_minus_90_enter.xml b/core/res/res/anim/screen_rotate_minus_90_enter.xml
index 87fd25e..b16d5fc 100644
--- a/core/res/res/anim/screen_rotate_minus_90_enter.xml
+++ b/core/res/res/anim/screen_rotate_minus_90_enter.xml
@@ -18,17 +18,19 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
+        android:shareInterpolator="false">
+    <!-- Version for two-phase anim
     <rotate android:fromDegrees="-90" android:toDegrees="0"
-        android:pivotX="50%" android:pivotY="50%"
-        android:fillEnabled="true"
-        android:fillBefore="true" android:fillAfter="true"
-        android:interpolator="@interpolator/fast_out_slow_in"
-        android:duration="@android:integer/config_screen_rotation_total_90" />
-    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
-        android:fillEnabled="true"
-        android:fillBefore="true" android:fillAfter="true"
-        android:interpolator="@interpolator/screen_rotation_alpha_in"
-        android:startOffset="@android:integer/config_screen_rotation_fade_in_delay"
-        android:duration="@android:integer/config_screen_rotation_fade_in" />
+            android:pivotX="50%" android:pivotY="50%"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_longAnimTime" />
+    -->
+    <rotate android:fromDegrees="-90" android:toDegrees="0"
+            android:pivotX="50%" android:pivotY="50%"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:duration="@android:integer/config_mediumAnimTime" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_minus_90_exit.xml b/core/res/res/anim/screen_rotate_minus_90_exit.xml
index c3aee14..0927dd3 100644
--- a/core/res/res/anim/screen_rotate_minus_90_exit.xml
+++ b/core/res/res/anim/screen_rotate_minus_90_exit.xml
@@ -18,16 +18,26 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
+        android:shareInterpolator="false">
+    <!-- Version for two-phase animation
     <rotate android:fromDegrees="0" android:toDegrees="90"
-        android:pivotX="50%" android:pivotY="50%"
-        android:fillEnabled="true"
-        android:fillBefore="true" android:fillAfter="true"
-        android:interpolator="@interpolator/fast_out_slow_in"
-        android:duration="@android:integer/config_screen_rotation_total_90" />
-    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
-        android:fillEnabled="true"
-        android:fillBefore="true" android:fillAfter="true"
-        android:interpolator="@interpolator/screen_rotation_alpha_out"
-        android:duration="@android:integer/config_screen_rotation_fade_out" />
+            android:pivotX="50%" android:pivotY="50%"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_longAnimTime" />
+    -->
+    <scale android:fromXScale="100%" android:toXScale="100%p"
+            android:fromYScale="100%" android:toYScale="100%p"
+            android:pivotX="50%" android:pivotY="50%"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_mediumAnimTime" />
+    <rotate android:fromDegrees="0" android:toDegrees="90"
+            android:pivotX="50%" android:pivotY="50%"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_mediumAnimTime" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_plus_90_enter.xml b/core/res/res/anim/screen_rotate_plus_90_enter.xml
index 8849db4..86a8d24 100644
--- a/core/res/res/anim/screen_rotate_plus_90_enter.xml
+++ b/core/res/res/anim/screen_rotate_plus_90_enter.xml
@@ -18,16 +18,19 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
+        android:shareInterpolator="false">
+    <!-- Version for two-phase animation
     <rotate android:fromDegrees="90" android:toDegrees="0"
-        android:pivotX="50%" android:pivotY="50%"
-        android:fillEnabled="true"
-        android:fillBefore="true" android:fillAfter="true"
-        android:interpolator="@interpolator/fast_out_slow_in"
-        android:duration="@android:integer/config_screen_rotation_total_90" />
-    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
-        android:fillEnabled="true"
-        android:interpolator="@interpolator/screen_rotation_alpha_in"
-        android:startOffset="@android:integer/config_screen_rotation_fade_in_delay"
-        android:duration="@android:integer/config_screen_rotation_fade_in" />
+            android:pivotX="50%" android:pivotY="50%"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_longAnimTime" />
+    -->
+    <rotate android:fromDegrees="90" android:toDegrees="0"
+            android:pivotX="50%" android:pivotY="50%"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_mediumAnimTime" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_plus_90_exit.xml b/core/res/res/anim/screen_rotate_plus_90_exit.xml
index de84c3b..fd786f9 100644
--- a/core/res/res/anim/screen_rotate_plus_90_exit.xml
+++ b/core/res/res/anim/screen_rotate_plus_90_exit.xml
@@ -18,16 +18,26 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shareInterpolator="false">
+        android:shareInterpolator="false">
+    <!-- Version for two-phase animation
     <rotate android:fromDegrees="0" android:toDegrees="-90"
-        android:pivotX="50%" android:pivotY="50%"
-        android:fillEnabled="true"
-        android:fillBefore="true" android:fillAfter="true"
-        android:interpolator="@interpolator/fast_out_slow_in"
-        android:duration="@android:integer/config_screen_rotation_total_90" />
-    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
-        android:interpolator="@interpolator/screen_rotation_alpha_out"
-        android:fillEnabled="true"
-        android:fillBefore="true" android:fillAfter="true"
-        android:duration="@android:integer/config_screen_rotation_fade_out" />
+            android:pivotX="50%" android:pivotY="50%"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_longAnimTime" />
+    -->
+    <scale android:fromXScale="100%" android:toXScale="100%p"
+            android:fromYScale="100%" android:toYScale="100%p"
+            android:pivotX="50%" android:pivotY="50%"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_mediumAnimTime" />
+    <rotate android:fromDegrees="0" android:toDegrees="-90"
+            android:pivotX="50%" android:pivotY="50%"
+            android:interpolator="@interpolator/decelerate_quint"
+            android:fillEnabled="true"
+            android:fillBefore="true" android:fillAfter="true"
+            android:duration="@android:integer/config_mediumAnimTime" />
 </set>
diff --git a/core/res/res/interpolator/screen_rotation_alpha_in.xml b/core/res/res/interpolator/screen_rotation_alpha_in.xml
deleted file mode 100644
index 9c566a7..0000000
--- a/core/res/res/interpolator/screen_rotation_alpha_in.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:controlX1="0.15"
-    android:controlY1="0.45"
-    android:controlX2="0.33"
-    android:controlY2="1"/>
diff --git a/core/res/res/interpolator/screen_rotation_alpha_out.xml b/core/res/res/interpolator/screen_rotation_alpha_out.xml
deleted file mode 100644
index 73a37d4..0000000
--- a/core/res/res/interpolator/screen_rotation_alpha_out.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:controlX1="0.57"
-    android:controlY1="0"
-    android:controlX2="0.71"
-    android:controlY2=".43"/>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 245aed1..6691d4c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -147,24 +147,6 @@
     <integer name="config_activityShortDur">150</integer>
     <integer name="config_activityDefaultDur">220</integer>
 
-    <!-- Fade out time for screen rotation -->
-    <integer name="config_screen_rotation_fade_out">116</integer>
-
-    <!-- Fade in time for screen rotation -->
-    <integer name="config_screen_rotation_fade_in">233</integer>
-
-    <!-- Fade in delay time for screen rotation -->
-    <integer name="config_screen_rotation_fade_in_delay">100</integer>
-
-    <!-- Total time for 90 degree screen rotation animations -->
-    <integer name="config_screen_rotation_total_90">333</integer>
-
-    <!-- Total time for 180 degree screen rotation animation -->
-    <integer name="config_screen_rotation_total_180">433</integer>
-
-    <!-- Total time for the rotation background color transition -->
-    <integer name="config_screen_rotation_color_transition">200</integer>
-
     <!-- The duration (in milliseconds) of the tooltip show/hide animations. -->
     <integer name="config_tooltipAnimTime">150</integer>
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 817ccde..37c9710 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3000,7 +3000,6 @@
     <public-group type="attr" first-id="0x01010607">
       <public name="importantForContentCapture" />
       <public name="forceQueryable" />
-      <!-- @hide @SystemApi -->
       <public name="resourcesMap" />
       <public name="animatedImageDrawable"/>
       <public name="htmlDescription"/>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 973d5f6..9e11749 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1805,6 +1805,7 @@
   <!-- From services -->
   <java-symbol type="anim" name="screen_rotate_0_enter" />
   <java-symbol type="anim" name="screen_rotate_0_exit" />
+  <java-symbol type="anim" name="screen_rotate_0_frame" />
   <java-symbol type="anim" name="screen_rotate_180_enter" />
   <java-symbol type="anim" name="screen_rotate_180_exit" />
   <java-symbol type="anim" name="screen_rotate_180_frame" />
@@ -1980,7 +1981,6 @@
   <java-symbol type="integer" name="config_virtualKeyQuietTimeMillis" />
   <java-symbol type="integer" name="config_brightness_ramp_rate_fast" />
   <java-symbol type="integer" name="config_brightness_ramp_rate_slow" />
-  <java-symbol type="integer" name="config_screen_rotation_color_transition" />
   <java-symbol type="layout" name="am_compat_mode_dialog" />
   <java-symbol type="layout" name="launch_warning" />
   <java-symbol type="layout" name="safe_mode" />
diff --git a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
index 68b9b00..f4709ff 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
@@ -33,14 +33,15 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.support.test.uiautomator.UiDevice;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
 
-import libcore.io.Streams;
-
 import com.google.mockwebserver.MockResponse;
 import com.google.mockwebserver.MockWebServer;
 
+import libcore.io.Streams;
+
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.File;
@@ -63,6 +64,7 @@
     private static final String TAG = "DownloadManagerBaseTest";
     protected DownloadManager mDownloadManager = null;
     private MockWebServer mServer = null;
+    private UiDevice mUiDevice = null;
     protected String mFileType = "text/plain";
     protected Context mContext = null;
     protected MultipleDownloadsCompletedReceiver mReceiver = null;
@@ -234,6 +236,7 @@
     @Override
     public void setUp() throws Exception {
         mContext = getInstrumentation().getContext();
+        mUiDevice = UiDevice.getInstance(getInstrumentation());
         mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE);
         mServer = new MockWebServer();
         mServer.play();
@@ -512,7 +515,7 @@
         Log.i(LOG_TAG, "Setting WiFi State to: " + enable);
         WifiManager manager = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE);
 
-        manager.setWifiEnabled(enable);
+        mUiDevice.executeShellCommand("svc wifi " + (enable ? "enable" : "disable"));
 
         String timeoutMessage = "Timed out waiting for Wifi to be "
             + (enable ? "enabled!" : "disabled!");
diff --git a/core/tests/coretests/src/android/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java
index c6c0b46..b13bcd1 100644
--- a/core/tests/coretests/src/android/net/NetworkKeyTest.java
+++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.net.wifi.ScanResult;
@@ -107,7 +108,7 @@
 
     @Test
     public void createFromScanResult_nullSsid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.BSSID = VALID_BSSID;
 
         assertNull(NetworkKey.createFromScanResult(scanResult));
@@ -115,7 +116,7 @@
 
     @Test
     public void createFromScanResult_emptySsid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = "";
         scanResult.BSSID = VALID_BSSID;
 
@@ -124,7 +125,7 @@
 
     @Test
     public void createFromScanResult_noneSsid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = WifiManager.UNKNOWN_SSID;
         scanResult.BSSID = VALID_BSSID;
 
@@ -133,7 +134,7 @@
 
     @Test
     public void createFromScanResult_nullBssid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = VALID_UNQUOTED_SSID;
 
         assertNull(NetworkKey.createFromScanResult(scanResult));
@@ -141,7 +142,7 @@
 
     @Test
     public void createFromScanResult_emptyBssid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = VALID_UNQUOTED_SSID;
         scanResult.BSSID = "";
 
@@ -150,7 +151,7 @@
 
     @Test
     public void createFromScanResult_invalidBssid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = VALID_UNQUOTED_SSID;
         scanResult.BSSID = INVALID_BSSID;
 
@@ -159,7 +160,7 @@
 
     @Test
     public void createFromScanResult_validSsid() {
-        ScanResult scanResult = new ScanResult();
+        ScanResult scanResult = mock(ScanResult.class);
         scanResult.SSID = VALID_UNQUOTED_SSID;
         scanResult.BSSID = VALID_BSSID;
 
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 0e19ca8..d0fd92a 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -90,12 +90,12 @@
             mImeConsumer.onWindowFocusGained();
             mImeConsumer.applyImeVisibility(true);
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test if setVisibility can hide IME
             mImeConsumer.applyImeVisibility(false);
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
         });
     }
 
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 179929f..fa61a0a 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -16,11 +16,11 @@
 
 package android.view;
 
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.WindowInsets.Type.systemBars;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -39,8 +39,6 @@
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.test.InsetsModeSession;
 
-import androidx.test.runner.AndroidJUnit4;
-
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -52,6 +50,8 @@
 
 import java.util.List;
 
+import androidx.test.runner.AndroidJUnit4;
+
 /**
  * Tests for {@link InsetsAnimationControlImpl}.
  *
@@ -116,7 +116,7 @@
         mController = new InsetsAnimationControlImpl(controls,
                 new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
                 mMockController, 10 /* durationMs */,
-                false /* fade */);
+                false /* fade */, LAYOUT_INSETS_DURING_ANIMATION_SHOWN);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index a89fc1e..1db96b1 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -68,6 +68,7 @@
     private InsetsController mController;
     private SurfaceSession mSession = new SurfaceSession();
     private SurfaceControl mLeash;
+    private ViewRootImpl mViewRoot;
 
     @Before
     public void setup() {
@@ -77,13 +78,13 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             Context context = InstrumentationRegistry.getTargetContext();
             // cannot mock ViewRootImpl since it's final.
-            ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay());
+            mViewRoot = new ViewRootImpl(context, context.getDisplay());
             try {
-                viewRootImpl.setView(new TextView(context), new LayoutParams(), null);
+                mViewRoot.setView(new TextView(context), new LayoutParams(), null);
             } catch (BadTokenException e) {
                 // activity isn't running, we will ignore BadTokenException.
             }
-            mController = new InsetsController(viewRootImpl);
+            mController = new InsetsController(mViewRoot);
             final Rect rect = new Rect(5, 5, 5, 5);
             mController.calculateInsets(
                     false,
@@ -117,16 +118,22 @@
 
     @Test
     public void testControlsRevoked_duringAnim() {
-        InsetsSourceControl control =
-                new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point());
-        mController.onControlsChanged(new InsetsSourceControl[] { control });
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            InsetsSourceControl control =
+                    new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point());
+            mController.onControlsChanged(new InsetsSourceControl[] { control });
 
-        WindowInsetsAnimationControlListener mockListener =
-                mock(WindowInsetsAnimationControlListener.class);
-        mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */, mockListener);
-        verify(mockListener).onReady(any(), anyInt());
-        mController.onControlsChanged(new InsetsSourceControl[0]);
-        verify(mockListener).onCancelled();
+            WindowInsetsAnimationControlListener mockListener =
+                    mock(WindowInsetsAnimationControlListener.class);
+            mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */,
+                    mockListener);
+
+            // Ready gets deferred until next predraw
+            mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
+            verify(mockListener).onReady(any(), anyInt());
+            mController.onControlsChanged(new InsetsSourceControl[0]);
+            verify(mockListener).onCancelled();
+        });
     }
 
     @Test
@@ -154,16 +161,16 @@
             mController.show(Type.all());
             // quickly jump to final state by cancelling it.
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             mController.applyImeVisibility(false /* setVisible */);
             mController.hide(Type.all());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -180,10 +187,10 @@
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained();
             mController.applyImeVisibility(true);
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
             mController.applyImeVisibility(false);
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
             mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -201,16 +208,16 @@
             // test show select types.
             mController.show(types);
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test hide all
             mController.hide(types);
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -227,29 +234,29 @@
             // test show select types.
             mController.show(types);
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test hide all
             mController.hide(Type.all());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test single show
             mController.show(Type.navigationBars());
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             // test single hide
             mController.hide(Type.navigationBars());
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -267,31 +274,31 @@
             mController.show(Type.navigationBars());
             mController.show(Type.systemBars());
             mController.cancelExistingAnimation();
-            assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             mController.hide(Type.navigationBars());
             mController.hide(Type.systemBars());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             int types = Type.navigationBars() | Type.systemBars();
             // show two at a time and hide one by one.
             mController.show(types);
             mController.hide(Type.navigationBars());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             mController.hide(Type.systemBars());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -309,15 +316,15 @@
             mController.show(types);
             mController.hide(Type.navigationBars());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
 
             mController.hide(Type.systemBars());
             mController.cancelExistingAnimation();
-            assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
-            assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+            assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+            assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -336,12 +343,16 @@
 
             ArgumentCaptor<WindowInsetsAnimationController> controllerCaptor =
                     ArgumentCaptor.forClass(WindowInsetsAnimationController.class);
+
+            // Ready gets deferred until next predraw
+            mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
+
             verify(mockListener).onReady(controllerCaptor.capture(), anyInt());
             controllerCaptor.getValue().finish(false /* shown */);
         });
         waitUntilNextFrame();
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isVisible());
+            assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 7af833b..492c036 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -96,7 +96,7 @@
     @Test
     public void testHide() {
         mConsumer.hide();
-        assertFalse("Consumer should not be visible", mConsumer.isVisible());
+        assertFalse("Consumer should not be visible", mConsumer.isRequestedVisible());
         verify(mSpyInsetsSource).setVisible(eq(false));
     }
 
@@ -106,7 +106,7 @@
         // Insets source starts out visible
         mConsumer.hide();
         mConsumer.show();
-        assertTrue("Consumer should be visible", mConsumer.isVisible());
+        assertTrue("Consumer should be visible", mConsumer.isRequestedVisible());
         verify(mSpyInsetsSource).setVisible(eq(false));
         verify(mSpyInsetsSource).setVisible(eq(true));
     }
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index d7f50ba..e3b08bb 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -108,5 +108,30 @@
         assertEquals(Insets.of(0, 100, 0, 0), insets);
     }
 
+    @Test
+    public void testCalculateVisibleInsets_default() {
+        mSource.setFrame(new Rect(0, 0, 500, 100));
+        Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+        assertEquals(Insets.of(0, 100, 0, 0), insets);
+    }
+
+    @Test
+    public void testCalculateVisibleInsets_override() {
+        mSource.setFrame(new Rect(0, 0, 500, 100));
+        mSource.setVisibleFrame(new Rect(0, 0, 500, 200));
+        Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+        assertEquals(Insets.of(0, 200, 0, 0), insets);
+    }
+
+    @Test
+    public void testCalculateVisibleInsets_invisible() {
+        mSource.setFrame(new Rect(0, 0, 500, 100));
+        mSource.setVisibleFrame(new Rect(0, 0, 500, 200));
+        mSource.setVisible(false);
+        Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+        assertEquals(Insets.of(0, 0, 0, 0), insets);
+    }
+
+
     // Parcel and equals already tested via InsetsStateTest
 }
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index fa2ffcca..4b76fee 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -18,12 +18,14 @@
 
 import static android.view.InsetsState.ISIDE_BOTTOM;
 import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES;
 import static android.view.InsetsState.ITYPE_CAPTION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 
 import static org.junit.Assert.assertEquals;
@@ -209,6 +211,42 @@
         assertFalse(InsetsState.getDefaultVisibility(ITYPE_IME));
     }
 
+    @Test
+    public void testCalculateVisibleInsets() throws Exception {
+        try (final InsetsModeSession session =
+                     new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
+            mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+            mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+            mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+            mState.getSource(ITYPE_IME).setVisible(true);
+
+            // Make sure bottom gestures are ignored
+            mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
+            mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+            Rect visibleInsets = mState.calculateVisibleInsets(
+                    new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_PAN);
+            assertEquals(new Rect(0, 100, 0, 100), visibleInsets);
+        }
+    }
+
+    @Test
+    public void testCalculateVisibleInsets_adjustNothing() throws Exception {
+        try (final InsetsModeSession session =
+                     new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
+            mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+            mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+            mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+            mState.getSource(ITYPE_IME).setVisible(true);
+
+            // Make sure bottom gestures are ignored
+            mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
+            mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+            Rect visibleInsets = mState.calculateVisibleInsets(
+                    new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_NOTHING);
+            assertEquals(new Rect(0, 100, 0, 0), visibleInsets);
+        }
+    }
+
     private void assertEqualsAndHashCode() {
         assertEquals(mState, mState2);
         assertEquals(mState.hashCode(), mState2.hashCode());
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 3586216a..93f4b51 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -124,6 +124,10 @@
 
     public void setSoftKeyboardCallbackEnabled(boolean enabled) {}
 
+    public boolean switchToInputMethod(String imeId) {
+        return false;
+    }
+
     public boolean isAccessibilityButtonAvailable() {
         return false;
     }
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index f497db2..89c2374 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -226,6 +226,61 @@
     }
 
     @Test
+    public void testEditor_onTouchEvent_quickTapAfterDrag() throws Throwable {
+        String text = "Hi world!";
+        onView(withId(R.id.textview)).perform(replaceText(text));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+        TextView tv = mActivity.findViewById(R.id.textview);
+        Editor editor = tv.getEditorForTesting();
+
+        // Simulate a tap-and-drag gesture.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 5f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event2Time = 1002;
+        MotionEvent event2 = moveEvent(event1Time, event2Time, 50f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
+        assertTrue(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event3Time = 1003;
+        MotionEvent event3 = moveEvent(event1Time, event3Time, 100f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
+        assertTrue(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event4Time = 2004;
+        MotionEvent event4 = upEvent(event1Time, event4Time, 100f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        // Simulate a quick tap after the drag, near the location where the drag ended.
+        long event5Time = 2005;
+        MotionEvent event5 = downEvent(event5Time, event5Time, 90f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event6Time = 2006;
+        MotionEvent event6 = upEvent(event5Time, event6Time, 90f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event6));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        // Simulate another quick tap in the same location; now selection should be triggered.
+        long event7Time = 2007;
+        MotionEvent event7 = downEvent(event7Time, event7Time, 90f, 10f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event7));
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertTrue(editor.getSelectionController().isCursorBeingModified());
+    }
+
+    @Test
     public void testEditor_onTouchEvent_cursorDrag() throws Throwable {
         String text = "testEditor_onTouchEvent_cursorDrag";
         onView(withId(R.id.textview)).perform(replaceText(text));
@@ -237,29 +292,25 @@
         // Simulate a tap-and-drag gesture. This should trigger a cursor drag.
         long event1Time = 1001;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event1));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event2Time = 1002;
         MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event2));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event3Time = 1003;
-        MotionEvent event3 = moveEvent(event3Time, event3Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event3));
-        mInstrumentation.waitForIdleSync();
+        MotionEvent event3 = moveEvent(event1Time, event3Time, 120f, 30f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
         assertTrue(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event4Time = 1004;
-        MotionEvent event4 = upEvent(event3Time, event4Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event4));
-        mInstrumentation.waitForIdleSync();
+        MotionEvent event4 = upEvent(event1Time, event4Time, 120f, 30f);
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
     }
@@ -276,36 +327,31 @@
         // Simulate a double-tap followed by a drag. This should trigger a selection drag.
         long event1Time = 1001;
         MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event1));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event2Time = 1002;
         MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event2));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
 
         long event3Time = 1003;
         MotionEvent event3 = downEvent(event3Time, event3Time, 20f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event3));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertTrue(editor.getSelectionController().isCursorBeingModified());
 
         long event4Time = 1004;
         MotionEvent event4 = moveEvent(event3Time, event4Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event4));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertTrue(editor.getSelectionController().isCursorBeingModified());
 
         long event5Time = 1005;
         MotionEvent event5 = upEvent(event3Time, event5Time, 120f, 30f);
-        mActivity.runOnUiThread(() -> editor.onTouchEvent(event5));
-        mInstrumentation.waitForIdleSync();
+        mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5));
         assertFalse(editor.getInsertionController().isCursorBeingModified());
         assertFalse(editor.getSelectionController().isCursorBeingModified());
     }
diff --git a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
index 6adb1b8..215d0b8 100644
--- a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
+++ b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
@@ -120,6 +120,60 @@
     }
 
     @Test
+    public void testUpdate_doubleTap_delayAfterFirstDownEvent() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1000;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+
+        // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
+        long event2Time = 1000 + ViewConfiguration.getDoubleTapTimeout() + 1;
+        MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
+        mTouchState.update(event2, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+
+        // Generate an ACTION_DOWN event whose time is within the double-tap timeout when
+        // calculated from the last ACTION_UP event time. Even though the time between the last up
+        // and this down event is within the double-tap timeout, this should not be considered a
+        // double-tap (since the first down event had a longer delay).
+        long event3Time = event2Time + 1;
+        MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
+        mTouchState.update(event3, mConfig);
+        assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false);
+    }
+
+    @Test
+    public void testUpdate_quickTapAfterDrag() throws Exception {
+        // Simulate an ACTION_DOWN event.
+        long event1Time = 1000;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mTouchState.update(event1, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+
+        // Simulate an ACTION_MOVE event.
+        long event2Time = 1001;
+        MotionEvent event2 = moveEvent(event1Time, event2Time, 200f, 31f);
+        mTouchState.update(event2, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 0, 0, true);
+
+        // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
+        long event3Time = 5000;
+        MotionEvent event3 = upEvent(event1Time, event3Time, 200f, 31f);
+        mTouchState.update(event3, mConfig);
+        assertSingleTap(mTouchState, 20f, 30f, 200f, 31f, false);
+
+        // Generate an ACTION_DOWN event whose time is within the double-tap timeout when
+        // calculated from the last ACTION_UP event time. Even though the time between the last up
+        // and this down event is within the double-tap timeout, this should not be considered a
+        // double-tap (since the first down event had a longer delay).
+        long event4Time = event3Time + 1;
+        MotionEvent event4 = downEvent(event4Time, event4Time, 200f, 31f);
+        mTouchState.update(event4, mConfig);
+        assertSingleTap(mTouchState, 200f, 31f, 200f, 31f, false);
+    }
+
+    @Test
     public void testUpdate_tripleClick_mouse() throws Exception {
         // Simulate an ACTION_DOWN event.
         long event1Time = 1000;
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index ea66ee3..70d4678 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -1 +1 @@
-per-file privapp-permissions-platform.xml = hackbod@android.com, jsharkey@android.com, svetoslavganov@google.com, toddke@google.com, yamasani@google.com, cbrubaker@google.com, jeffv@google.com, moltmann@google.com
+per-file privapp-permissions-platform.xml = hackbod@android.com, jsharkey@android.com, svetoslavganov@google.com, toddke@google.com, yamasani@google.com, cbrubaker@google.com, jeffv@google.com, moltmann@google.com, lorenzo@google.com
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index eb1d1ab..9930ea2 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -243,6 +243,7 @@
         <permission name="android.permission.MANAGE_USB"/>
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
+        <permission name="android.permission.TETHER_PRIVILEGED"/>
         <permission name="android.permission.UPDATE_APP_OPS_STATS"/>
     </privapp-permissions>
 
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index d945fc4..a7f8cc4 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -215,6 +215,7 @@
         android: {
 
             srcs: [
+                "pipeline/skia/ATraceMemoryDump.cpp",
                 "pipeline/skia/GLFunctorDrawable.cpp",
                 "pipeline/skia/LayerDrawable.cpp",
                 "pipeline/skia/ShaderCache.cpp",
@@ -244,7 +245,6 @@
                 "DeviceInfo.cpp",
                 "FrameInfo.cpp",
                 "FrameInfoVisualizer.cpp",
-                "GpuMemoryTracker.cpp",
                 "HardwareBitmapUploader.cpp",
                 "HWUIProperties.sysprop",
                 "JankTracker.cpp",
@@ -325,7 +325,6 @@
         "tests/unit/DamageAccumulatorTests.cpp",
         "tests/unit/DeferredLayerUpdaterTests.cpp",
         "tests/unit/FatVectorTests.cpp",
-        "tests/unit/GpuMemoryTrackerTests.cpp",
         "tests/unit/GraphicsStatsServiceTests.cpp",
         "tests/unit/LayerUpdateQueueTests.cpp",
         "tests/unit/LinearAllocatorTests.cpp",
diff --git a/libs/hwui/GpuMemoryTracker.cpp b/libs/hwui/GpuMemoryTracker.cpp
deleted file mode 100644
index a9a7af8..0000000
--- a/libs/hwui/GpuMemoryTracker.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "utils/StringUtils.h"
-
-#include <GpuMemoryTracker.h>
-#include <cutils/compiler.h>
-#include <utils/Trace.h>
-#include <array>
-#include <sstream>
-#include <unordered_set>
-#include <vector>
-
-namespace android {
-namespace uirenderer {
-
-pthread_t gGpuThread = 0;
-
-#define NUM_TYPES static_cast<int>(GpuObjectType::TypeCount)
-
-const char* TYPE_NAMES[] = {
-        "Texture", "OffscreenBuffer", "Layer",
-};
-
-struct TypeStats {
-    int totalSize = 0;
-    int count = 0;
-};
-
-static std::array<TypeStats, NUM_TYPES> gObjectStats;
-static std::unordered_set<GpuMemoryTracker*> gObjectSet;
-
-void GpuMemoryTracker::notifySizeChanged(int newSize) {
-    int delta = newSize - mSize;
-    mSize = newSize;
-    gObjectStats[static_cast<int>(mType)].totalSize += delta;
-}
-
-void GpuMemoryTracker::startTrackingObject() {
-    auto result = gObjectSet.insert(this);
-    LOG_ALWAYS_FATAL_IF(!result.second,
-                        "startTrackingObject() on %p failed, already being tracked!", this);
-    gObjectStats[static_cast<int>(mType)].count++;
-}
-
-void GpuMemoryTracker::stopTrackingObject() {
-    size_t removed = gObjectSet.erase(this);
-    LOG_ALWAYS_FATAL_IF(removed != 1, "stopTrackingObject removed %zd, is %p not being tracked?",
-                        removed, this);
-    gObjectStats[static_cast<int>(mType)].count--;
-}
-
-void GpuMemoryTracker::onGpuContextCreated() {
-    LOG_ALWAYS_FATAL_IF(gGpuThread != 0,
-                        "We already have a gpu thread? "
-                        "current = %lu, gpu thread = %lu",
-                        pthread_self(), gGpuThread);
-    gGpuThread = pthread_self();
-}
-
-void GpuMemoryTracker::onGpuContextDestroyed() {
-    gGpuThread = 0;
-    if (CC_UNLIKELY(gObjectSet.size() > 0)) {
-        std::stringstream os;
-        dump(os);
-        ALOGE("%s", os.str().c_str());
-        LOG_ALWAYS_FATAL("Leaked %zd GPU objects!", gObjectSet.size());
-    }
-}
-
-void GpuMemoryTracker::dump() {
-    std::stringstream strout;
-    dump(strout);
-    ALOGD("%s", strout.str().c_str());
-}
-
-void GpuMemoryTracker::dump(std::ostream& stream) {
-    for (int type = 0; type < NUM_TYPES; type++) {
-        const TypeStats& stats = gObjectStats[type];
-        stream << TYPE_NAMES[type];
-        stream << " is using " << SizePrinter{stats.totalSize};
-        stream << ", count = " << stats.count;
-        stream << std::endl;
-    }
-}
-
-int GpuMemoryTracker::getInstanceCount(GpuObjectType type) {
-    return gObjectStats[static_cast<int>(type)].count;
-}
-
-int GpuMemoryTracker::getTotalSize(GpuObjectType type) {
-    return gObjectStats[static_cast<int>(type)].totalSize;
-}
-
-void GpuMemoryTracker::onFrameCompleted() {
-    if (ATRACE_ENABLED()) {
-        char buf[128];
-        for (int type = 0; type < NUM_TYPES; type++) {
-            snprintf(buf, 128, "hwui_%s", TYPE_NAMES[type]);
-            const TypeStats& stats = gObjectStats[type];
-            ATRACE_INT(buf, stats.totalSize);
-            snprintf(buf, 128, "hwui_%s_count", TYPE_NAMES[type]);
-            ATRACE_INT(buf, stats.count);
-        }
-    }
-}
-
-}  // namespace uirenderer
-}  // namespace android;
diff --git a/libs/hwui/GpuMemoryTracker.h b/libs/hwui/GpuMemoryTracker.h
deleted file mode 100644
index de3ca99..0000000
--- a/libs/hwui/GpuMemoryTracker.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#include <pthread.h>
-#include <ostream>
-
-#include <log/log.h>
-
-namespace android {
-namespace uirenderer {
-
-extern pthread_t gGpuThread;
-
-#define ASSERT_GPU_THREAD()                                                                    \
-    LOG_ALWAYS_FATAL_IF(!pthread_equal(gGpuThread, pthread_self()),                            \
-                        "Error, %p of type %d (size=%d) used on wrong thread! cur thread %lu " \
-                        "!= gpu thread %lu",                                                   \
-                        this, static_cast<int>(mType), mSize, pthread_self(), gGpuThread)
-
-enum class GpuObjectType {
-    Texture = 0,
-    OffscreenBuffer,
-    Layer,
-
-    TypeCount,
-};
-
-class GpuMemoryTracker {
-public:
-    GpuObjectType objectType() { return mType; }
-    int objectSize() { return mSize; }
-
-    static void onGpuContextCreated();
-    static void onGpuContextDestroyed();
-    static void dump();
-    static void dump(std::ostream& stream);
-    static int getInstanceCount(GpuObjectType type);
-    static int getTotalSize(GpuObjectType type);
-    static void onFrameCompleted();
-
-protected:
-    explicit GpuMemoryTracker(GpuObjectType type) : mType(type) {
-        ASSERT_GPU_THREAD();
-        startTrackingObject();
-    }
-
-    ~GpuMemoryTracker() {
-        notifySizeChanged(0);
-        stopTrackingObject();
-    }
-
-    void notifySizeChanged(int newSize);
-
-private:
-    void startTrackingObject();
-    void stopTrackingObject();
-
-    int mSize = 0;
-    GpuObjectType mType;
-};
-
-}  // namespace uirenderer
-}  // namespace android;
diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp
index 7921662..a8e36e3 100644
--- a/libs/hwui/ProfileData.cpp
+++ b/libs/hwui/ProfileData.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "ProfileData.h"
+#include "Properties.h"
 
 #include <cinttypes>
 
@@ -102,6 +103,7 @@
         mGPUFrameCounts[i] >>= divider;
         mGPUFrameCounts[i] += other.mGPUFrameCounts[i];
     }
+    mPipelineType = other.mPipelineType;
 }
 
 void ProfileData::dump(int fd) const {
@@ -157,6 +159,7 @@
     mTotalFrameCount = 0;
     mJankFrameCount = 0;
     mStatStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    mPipelineType = Properties::getRenderPipelineType();
 }
 
 void ProfileData::reportFrame(int64_t duration) {
diff --git a/libs/hwui/ProfileData.h b/libs/hwui/ProfileData.h
index ccbffc6..dd3ba66 100644
--- a/libs/hwui/ProfileData.h
+++ b/libs/hwui/ProfileData.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include "Properties.h"
 #include "utils/Macros.h"
 
 #include <utils/Timers.h>
@@ -65,6 +66,7 @@
     uint32_t jankFrameCount() const { return mJankFrameCount; }
     nsecs_t statsStartTime() const { return mStatStartTime; }
     uint32_t jankTypeCount(JankType type) const { return mJankTypeCounts[static_cast<int>(type)]; }
+    RenderPipelineType pipelineType() const { return mPipelineType; }
 
     struct HistogramEntry {
         uint32_t renderTimeMs;
@@ -103,6 +105,9 @@
     uint32_t mTotalFrameCount;
     uint32_t mJankFrameCount;
     nsecs_t mStatStartTime;
+
+    // true if HWUI renders with Vulkan pipeline
+    RenderPipelineType mPipelineType;
 };
 
 // For testing
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
new file mode 100644
index 0000000..2c78b02
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ATraceMemoryDump.h"
+
+#include <utils/Trace.h>
+
+#include <cstring>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+// When purgeable is INVALID_TIME it won't be logged at all.
+#define INVALID_TIME -1
+
+/**
+ * Skia invokes the following SkTraceMemoryDump functions:
+ * 1. dumpNumericValue (dumpName, units="bytes", valueName="size")
+ * 2. dumpStringValue (dumpName, valueName="type") [optional -> for example CPU memory does not
+ * invoke dumpStringValue]
+ * 3. dumpNumericValue (dumpName, units="bytes", valueName="purgeable_size") [optional]
+ * 4. setMemoryBacking(dumpName, backingType) [optional -> for example Vulkan GPU resources do not
+ * invoke setMemoryBacking]
+ *
+ * ATraceMemoryDump calculates memory category first by looking at the "type" string passed to
+ * dumpStringValue and then by looking at "backingType" passed to setMemoryBacking.
+ * Only GPU Texture memory is tracked separately and everything else is grouped as one
+ * "GPU Memory" category.
+ */
+static std::unordered_map<const char*, const char*> sResourceMap = {
+        {"malloc", "Graphics CPU Memory"},          // taken from setMemoryBacking(backingType)
+        {"gl_texture", "Graphics Texture Memory"},  // taken from setMemoryBacking(backingType)
+        {"Texture",
+         "Graphics Texture Memory"},  // taken from dumpStringValue(value, valueName="type")
+        // Uncomment categories below to split "GPU Memory" into more brackets for debugging.
+        /*{"vk_buffer", "vk_buffer"},
+        {"gl_renderbuffer", "gl_renderbuffer"},
+        {"gl_buffer", "gl_buffer"},
+        {"RenderTarget", "RenderTarget"},
+        {"Stencil", "Stencil"},
+        {"Path Data", "Path Data"},
+        {"Buffer Object", "Buffer Object"},
+        {"Surface", "Surface"},*/
+};
+
+ATraceMemoryDump::ATraceMemoryDump() {
+    mLastDumpName.reserve(100);
+    mCategory.reserve(100);
+}
+
+void ATraceMemoryDump::dumpNumericValue(const char* dumpName, const char* valueName,
+                                        const char* units, uint64_t value) {
+    if (!strcmp(units, "bytes")) {
+        recordAndResetCountersIfNeeded(dumpName);
+        if (!strcmp(valueName, "size")) {
+            mLastDumpValue = value;
+        } else if (!strcmp(valueName, "purgeable_size")) {
+            mLastPurgeableDumpValue = value;
+        }
+    }
+}
+
+void ATraceMemoryDump::dumpStringValue(const char* dumpName, const char* valueName,
+                                       const char* value) {
+    if (!strcmp(valueName, "type")) {
+        recordAndResetCountersIfNeeded(dumpName);
+        auto categoryIt = sResourceMap.find(value);
+        if (categoryIt != sResourceMap.end()) {
+            mCategory = categoryIt->second;
+        }
+    }
+}
+
+void ATraceMemoryDump::setMemoryBacking(const char* dumpName, const char* backingType,
+                                        const char* backingObjectId) {
+    recordAndResetCountersIfNeeded(dumpName);
+    auto categoryIt = sResourceMap.find(backingType);
+    if (categoryIt != sResourceMap.end()) {
+        mCategory = categoryIt->second;
+    }
+}
+
+/**
+ * startFrame is invoked before dumping anything. It resets counters from the previous frame.
+ * This is important, because if there is no new data for a given category trace would assume
+ * usage has not changed (instead of reporting 0).
+ */
+void ATraceMemoryDump::startFrame() {
+    resetCurrentCounter("");
+    for (auto& it : mCurrentValues) {
+        // Once a category is observed in at least one frame, it is always reported in subsequent
+        // frames (even if it is 0). Not logging a category to ATRACE would mean its value has not
+        // changed since the previous frame, which is not what we want.
+        it.second.time = 0;
+        // If purgeableTime is INVALID_TIME, then logTraces won't log it at all.
+        if (it.second.purgeableTime != INVALID_TIME) {
+            it.second.purgeableTime = 0;
+        }
+    }
+}
+
+/**
+ * logTraces reads from mCurrentValues and logs the counters with ATRACE.
+ */
+void ATraceMemoryDump::logTraces() {
+    // Accumulate data from last dumpName
+    recordAndResetCountersIfNeeded("");
+    for (auto& it : mCurrentValues) {
+        ATRACE_INT64(it.first.c_str(), it.second.time);
+        if (it.second.purgeableTime != INVALID_TIME) {
+            ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableTime);
+        }
+    }
+}
+
+/**
+ * recordAndResetCountersIfNeeded reads memory usage from mLastDumpValue/mLastPurgeableDumpValue and
+ * accumulates in mCurrentValues[category]. It makes provision to create a new category and track
+ * purgeable memory only if there is at least one observation.
+ * recordAndResetCountersIfNeeded won't do anything until all the information for a given dumpName
+ * is received.
+ */
+void ATraceMemoryDump::recordAndResetCountersIfNeeded(const char* dumpName) {
+    if (!mLastDumpName.compare(dumpName)) {
+        // Still waiting for more data for current dumpName.
+        return;
+    }
+
+    // First invocation will have an empty mLastDumpName.
+    if (!mLastDumpName.empty()) {
+        // A new dumpName observed -> store the data already collected.
+        auto memoryCounter = mCurrentValues.find(mCategory);
+        if (memoryCounter != mCurrentValues.end()) {
+            memoryCounter->second.time += mLastDumpValue;
+            if (mLastPurgeableDumpValue != INVALID_TIME) {
+                if (memoryCounter->second.purgeableTime == INVALID_TIME) {
+                    memoryCounter->second.purgeableTime = mLastPurgeableDumpValue;
+                } else {
+                    memoryCounter->second.purgeableTime += mLastPurgeableDumpValue;
+                }
+            }
+        } else {
+            mCurrentValues[mCategory] = {mLastDumpValue, mLastPurgeableDumpValue};
+        }
+    }
+
+    // Reset counters and default category for the newly observed "dumpName".
+    resetCurrentCounter(dumpName);
+}
+
+void ATraceMemoryDump::resetCurrentCounter(const char* dumpName) {
+    mLastDumpValue = 0;
+    mLastPurgeableDumpValue = INVALID_TIME;
+    mLastDumpName = dumpName;
+    // Categories not listed in sResourceMap are reported as "GPU memory"
+    mCategory = "GPU Memory";
+}
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.h b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
new file mode 100644
index 0000000..aa5c401
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <SkString.h>
+#include <SkTraceMemoryDump.h>
+
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class ATraceMemoryDump : public SkTraceMemoryDump {
+public:
+    ATraceMemoryDump();
+    ~ATraceMemoryDump() override {}
+
+    void dumpNumericValue(const char* dumpName, const char* valueName, const char* units,
+                          uint64_t value) override;
+
+    void dumpStringValue(const char* dumpName, const char* valueName, const char* value) override;
+
+    LevelOfDetail getRequestedDetails() const override {
+        return SkTraceMemoryDump::kLight_LevelOfDetail;
+    }
+
+    bool shouldDumpWrappedObjects() const override { return false; }
+
+    void setMemoryBacking(const char* dumpName, const char* backingType,
+                          const char* backingObjectId) override;
+
+    void setDiscardableMemoryBacking(const char*, const SkDiscardableMemory&) override {}
+
+    void startFrame();
+
+    void logTraces();
+
+private:
+    std::string mLastDumpName;
+
+    uint64_t mLastDumpValue;
+
+    uint64_t mLastPurgeableDumpValue;
+
+    std::string mCategory;
+
+    struct TraceValue {
+        uint64_t time;
+        uint64_t purgeableTime;
+    };
+
+    // keys are define in sResourceMap
+    std::unordered_map<std::string, TraceValue> mCurrentValues;
+
+    void recordAndResetCountersIfNeeded(const char* dumpName);
+
+    void resetCurrentCounter(const char* dumpName);
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 11dc013..35a885f 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -457,7 +457,10 @@
                                    const Rect& contentDrawBounds, SkCanvas* canvas,
                                    const SkMatrix& preTransform) {
     SkAutoCanvasRestore saver(canvas, true);
-    canvas->androidFramework_setDeviceClipRestriction(preTransform.mapRect(clip).roundOut());
+    auto clipRestriction = preTransform.mapRect(clip).roundOut();
+    canvas->androidFramework_setDeviceClipRestriction(clipRestriction);
+    canvas->drawAnnotation(SkRect::Make(clipRestriction), "AndroidDeviceClipRestriction",
+        nullptr);
     canvas->concat(preTransform);
 
     // STOPSHIP: Revert, temporary workaround to clear always F16 frame buffer for b/74976293
diff --git a/libs/hwui/protos/graphicsstats.proto b/libs/hwui/protos/graphicsstats.proto
index 0cd5c62..dd5676c 100644
--- a/libs/hwui/protos/graphicsstats.proto
+++ b/libs/hwui/protos/graphicsstats.proto
@@ -29,6 +29,11 @@
 }
 
 message GraphicsStatsProto {
+    enum PipelineType {
+        GL = 0;
+        VULKAN = 1;
+    }
+
     // The package name of the app
     optional string package_name = 1;
 
@@ -49,6 +54,9 @@
 
     // The gpu frame time histogram for the package
     repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7;
+
+    // HWUI renders pipeline type: GL or Vulkan
+    optional PipelineType pipeline = 8;
 }
 
 message GraphicsStatsJankSummaryProto {
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index fad9440..7e8c96d 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -16,7 +16,6 @@
 #include "renderstate/RenderState.h"
 
 #include "renderthread/RenderThread.h"
-#include "GpuMemoryTracker.h"
 
 namespace android {
 namespace uirenderer {
@@ -25,15 +24,10 @@
     mThreadId = pthread_self();
 }
 
-void RenderState::onContextCreated() {
-    GpuMemoryTracker::onGpuContextCreated();
-}
-
 void RenderState::onContextDestroyed() {
     for(auto callback : mContextCallbacks) {
         callback->onContextDestroyed();
     }
-    GpuMemoryTracker::onGpuContextDestroyed();
 }
 
 void RenderState::postDecStrong(VirtualLightRefBase* object) {
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index ff5d02f..e08d32a 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -62,7 +62,6 @@
     ~RenderState() {}
 
     // Context notifications are only to be triggered by renderthread::RenderThread
-    void onContextCreated();
     void onContextDestroyed();
 
     std::set<IGpuContextCallback*> mContextCallbacks;
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index eaed46c..d177855 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -20,10 +20,12 @@
 #include "Layer.h"
 #include "Properties.h"
 #include "RenderThread.h"
+#include "pipeline/skia/ATraceMemoryDump.h"
 #include "pipeline/skia/ShaderCache.h"
 #include "pipeline/skia/SkiaMemoryTracer.h"
 #include "renderstate/RenderState.h"
 #include "thread/CommonPool.h"
+#include <utils/Trace.h>
 
 #include <GrContextOptions.h>
 #include <SkExecutor.h>
@@ -184,6 +186,18 @@
     gpuTracer.logTotals(log);
 }
 
+void CacheManager::onFrameCompleted() {
+    if (ATRACE_ENABLED()) {
+        static skiapipeline::ATraceMemoryDump tracer;
+        tracer.startFrame();
+        SkGraphics::DumpMemoryStatistics(&tracer);
+        if (mGrContext) {
+            mGrContext->dumpMemoryStatistics(&tracer);
+        }
+        tracer.logTraces();
+    }
+}
+
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index 968251e..b009cc4 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -50,6 +50,7 @@
 
     size_t getCacheSize() const { return mMaxResourceBytes; }
     size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; }
+    void onFrameCompleted();
 
 private:
     friend class RenderThread;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 8490221..5993e17 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -16,7 +16,6 @@
 
 #include "CanvasContext.h"
 
-#include <GpuMemoryTracker.h>
 #include <apex/window.h>
 #include <fcntl.h>
 #include <strings.h>
@@ -558,7 +557,7 @@
         mJankTracker.finishGpuDraw(*forthBehind);
     }
 
-    GpuMemoryTracker::onFrameCompleted();
+    mRenderThread.cacheManager().onFrameCompleted();
 }
 
 // Called by choreographer to do an RT-driven animation
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index d78f641..cae3e3b 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -270,7 +270,6 @@
     }
     mGrContext = std::move(context);
     if (mGrContext) {
-        mRenderState->onContextCreated();
         DeviceInfo::setMaxTextureSize(mGrContext->maxRenderTargetSize());
     }
 }
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index 12c5b83..c418617 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -16,24 +16,28 @@
 
 #include "GraphicsStatsService.h"
 
-#include "JankTracker.h"
-#include "protos/graphicsstats.pb.h"
-
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
-#include <log/log.h>
-
 #include <errno.h>
 #include <fcntl.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
 #include <inttypes.h>
+#include <log/log.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <algorithm>
+#include <map>
+#include <vector>
+
+#include "JankTracker.h"
+#include "protos/graphicsstats.pb.h"
+
 namespace android {
 namespace uirenderer {
 
 using namespace google::protobuf;
+using namespace uirenderer::protos;
 
 constexpr int32_t sCurrentFileVersion = 1;
 constexpr int32_t sHeaderSize = 4;
@@ -42,9 +46,9 @@
 constexpr int sHistogramSize = ProfileData::HistogramSize();
 constexpr int sGPUHistogramSize = ProfileData::GPUHistogramSize();
 
-static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto,
-                                      const std::string& package, int64_t versionCode,
-                                      int64_t startTime, int64_t endTime, const ProfileData* data);
+static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package,
+                                      int64_t versionCode, int64_t startTime, int64_t endTime,
+                                      const ProfileData* data);
 static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd);
 
 class FileDescriptor {
@@ -57,7 +61,7 @@
         }
     }
     bool valid() { return mFd != -1; }
-    operator int() { return mFd; } // NOLINT(google-explicit-constructor)
+    operator int() { return mFd; }  // NOLINT(google-explicit-constructor)
 
 private:
     int mFd;
@@ -167,6 +171,8 @@
     }
     proto->set_package_name(package);
     proto->set_version_code(versionCode);
+    proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ?
+            GraphicsStatsProto_PipelineType_GL : GraphicsStatsProto_PipelineType_VULKAN);
     auto summary = proto->mutable_summary();
     summary->set_total_frames(summary->total_frames() + data->totalFrameCount());
     summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount());
@@ -179,8 +185,8 @@
     summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
                                           data->jankTypeCount(kSlowSync));
     summary->set_slow_draw_count(summary->slow_draw_count() + data->jankTypeCount(kSlowRT));
-    summary->set_missed_deadline_count(summary->missed_deadline_count()
-            + data->jankTypeCount(kMissedDeadline));
+    summary->set_missed_deadline_count(summary->missed_deadline_count() +
+                                       data->jankTypeCount(kMissedDeadline));
 
     bool creatingHistogram = false;
     if (proto->histogram_size() == 0) {
@@ -365,17 +371,69 @@
 
 class GraphicsStatsService::Dump {
 public:
-    Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
+    Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {
+        if (mFd == -1 && mType == DumpType::Protobuf) {
+            mType = DumpType::ProtobufStatsd;
+        }
+    }
     int fd() { return mFd; }
     DumpType type() { return mType; }
     protos::GraphicsStatsServiceDumpProto& proto() { return mProto; }
+    void mergeStat(const protos::GraphicsStatsProto& stat);
+    void updateProto();
 
 private:
+    // use package name and app version for a key
+    typedef std::pair<std::string, int64_t> DumpKey;
+
+    std::map<DumpKey, protos::GraphicsStatsProto> mStats;
     int mFd;
     DumpType mType;
     protos::GraphicsStatsServiceDumpProto mProto;
 };
 
+void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) {
+    auto dumpKey = std::make_pair(stat.package_name(), stat.version_code());
+    auto findIt = mStats.find(dumpKey);
+    if (findIt == mStats.end()) {
+        mStats[dumpKey] = stat;
+    } else {
+        auto summary = findIt->second.mutable_summary();
+        summary->set_total_frames(summary->total_frames() + stat.summary().total_frames());
+        summary->set_janky_frames(summary->janky_frames() + stat.summary().janky_frames());
+        summary->set_missed_vsync_count(summary->missed_vsync_count() +
+                                        stat.summary().missed_vsync_count());
+        summary->set_high_input_latency_count(summary->high_input_latency_count() +
+                                              stat.summary().high_input_latency_count());
+        summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() +
+                                          stat.summary().slow_ui_thread_count());
+        summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
+                                              stat.summary().slow_bitmap_upload_count());
+        summary->set_slow_draw_count(summary->slow_draw_count() + stat.summary().slow_draw_count());
+        summary->set_missed_deadline_count(summary->missed_deadline_count() +
+                                           stat.summary().missed_deadline_count());
+        for (int bucketIndex = 0; bucketIndex < findIt->second.histogram_size(); bucketIndex++) {
+            auto bucket = findIt->second.mutable_histogram(bucketIndex);
+            bucket->set_frame_count(bucket->frame_count() +
+                                    stat.histogram(bucketIndex).frame_count());
+        }
+        for (int bucketIndex = 0; bucketIndex < findIt->second.gpu_histogram_size();
+             bucketIndex++) {
+            auto bucket = findIt->second.mutable_gpu_histogram(bucketIndex);
+            bucket->set_frame_count(bucket->frame_count() +
+                                    stat.gpu_histogram(bucketIndex).frame_count());
+        }
+        findIt->second.set_stats_start(std::min(findIt->second.stats_start(), stat.stats_start()));
+        findIt->second.set_stats_end(std::max(findIt->second.stats_end(), stat.stats_end()));
+    }
+}
+
+void GraphicsStatsService::Dump::updateProto() {
+    for (auto& stat : mStats) {
+        mProto.add_stats()->CopyFrom(stat.second);
+    }
+}
+
 GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
     return new Dump(outFd, type);
 }
@@ -396,8 +454,9 @@
               path.empty() ? "<empty>" : path.c_str(), data);
         return;
     }
-
-    if (dump->type() == DumpType::Protobuf) {
+    if (dump->type() == DumpType::ProtobufStatsd) {
+        dump->mergeStat(statsProto);
+    } else if (dump->type() == DumpType::Protobuf) {
         dump->proto().add_stats()->CopyFrom(statsProto);
     } else {
         dumpAsTextToFd(&statsProto, dump->fd());
@@ -409,7 +468,9 @@
     if (!parseFromFile(path, &statsProto)) {
         return;
     }
-    if (dump->type() == DumpType::Protobuf) {
+    if (dump->type() == DumpType::ProtobufStatsd) {
+        dump->mergeStat(statsProto);
+    } else if (dump->type() == DumpType::Protobuf) {
         dump->proto().add_stats()->CopyFrom(statsProto);
     } else {
         dumpAsTextToFd(&statsProto, dump->fd());
@@ -424,5 +485,79 @@
     delete dump;
 }
 
+class MemOutputStreamLite : public io::ZeroCopyOutputStream {
+public:
+    explicit MemOutputStreamLite() : mCopyAdapter(), mImpl(&mCopyAdapter) {}
+    virtual ~MemOutputStreamLite() {}
+
+    virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); }
+
+    virtual void BackUp(int count) override { mImpl.BackUp(count); }
+
+    virtual int64 ByteCount() const override { return mImpl.ByteCount(); }
+
+    bool Flush() { return mImpl.Flush(); }
+
+    void copyData(const DumpMemoryFn& reader, void* param1, void* param2) {
+        int bufferOffset = 0;
+        int totalSize = mCopyAdapter.mBuffersSize - mCopyAdapter.mCurrentBufferUnusedSize;
+        int totalDataLeft = totalSize;
+        for (auto& it : mCopyAdapter.mBuffers) {
+            int bufferSize = std::min(totalDataLeft, (int)it.size());  // last buffer is not full
+            reader(it.data(), bufferOffset, bufferSize, totalSize, param1, param2);
+            bufferOffset += bufferSize;
+            totalDataLeft -= bufferSize;
+        }
+    }
+
+private:
+    struct MemAdapter : public io::CopyingOutputStream {
+        // Data is stored in an array of buffers.
+        // JNI SetByteArrayRegion assembles data in one continuous Java byte[] buffer.
+        std::vector<std::vector<unsigned char>> mBuffers;
+        int mBuffersSize = 0;                     // total bytes allocated in mBuffers
+        int mCurrentBufferUnusedSize = 0;         // unused bytes in the last buffer mBuffers.back()
+        unsigned char* mCurrentBuffer = nullptr;  // pointer to next free byte in mBuffers.back()
+
+        explicit MemAdapter() {}
+        virtual ~MemAdapter() {}
+
+        virtual bool Write(const void* buffer, int size) override {
+            while (size > 0) {
+                if (0 == mCurrentBufferUnusedSize) {
+                    mCurrentBufferUnusedSize =
+                            std::max(size, mBuffersSize ? 2 * mBuffersSize : 10000);
+                    mBuffers.emplace_back();
+                    mBuffers.back().resize(mCurrentBufferUnusedSize);
+                    mCurrentBuffer = mBuffers.back().data();
+                    mBuffersSize += mCurrentBufferUnusedSize;
+                }
+                int dataMoved = std::min(mCurrentBufferUnusedSize, size);
+                memcpy(mCurrentBuffer, buffer, dataMoved);
+                mCurrentBufferUnusedSize -= dataMoved;
+                mCurrentBuffer += dataMoved;
+                buffer = reinterpret_cast<const unsigned char*>(buffer) + dataMoved;
+                size -= dataMoved;
+            }
+            return true;
+        }
+    };
+
+    MemOutputStreamLite::MemAdapter mCopyAdapter;
+    io::CopyingOutputStreamAdaptor mImpl;
+};
+
+void GraphicsStatsService::finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1,
+                                              void* param2) {
+    MemOutputStreamLite stream;
+    dump->updateProto();
+    bool success = dump->proto().SerializeToZeroCopyStream(&stream) && stream.Flush();
+    delete dump;
+    if (!success) {
+        return;
+    }
+    stream.copyData(reader, param1, param2);
+}
+
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h
index 389f599..4bed9633 100644
--- a/libs/hwui/service/GraphicsStatsService.h
+++ b/libs/hwui/service/GraphicsStatsService.h
@@ -27,6 +27,9 @@
 class GraphicsStatsProto;
 }
 
+typedef void (*DumpMemoryFn)(void* buffer, int bufferOffset, int bufferSize, int totalSize,
+                             void* param1, void* param2);
+
 /*
  * The exported entry points used by GraphicsStatsService.java in f/b/services/core
  *
@@ -40,6 +43,7 @@
     enum class DumpType {
         Text,
         Protobuf,
+        ProtobufStatsd,
     };
 
     ANDROID_API static void saveBuffer(const std::string& path, const std::string& package,
@@ -52,6 +56,8 @@
                                       int64_t startTime, int64_t endTime, const ProfileData* data);
     ANDROID_API static void addToDump(Dump* dump, const std::string& path);
     ANDROID_API static void finishDump(Dump* dump);
+    ANDROID_API static void finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1,
+                                               void* param2);
 
     // Visible for testing
     static bool parseFromFile(const std::string& path, protos::GraphicsStatsProto* output);
diff --git a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
deleted file mode 100644
index dac888c..0000000
--- a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <GpuMemoryTracker.h>
-#include <gtest/gtest.h>
-
-#include "renderthread/EglManager.h"
-#include "renderthread/RenderThread.h"
-#include "tests/common/TestUtils.h"
-
-#include <utils/StrongPointer.h>
-
-using namespace android;
-using namespace android::uirenderer;
-using namespace android::uirenderer::renderthread;
-
-class TestGPUObject : public GpuMemoryTracker {
-public:
-    TestGPUObject() : GpuMemoryTracker(GpuObjectType::Texture) {}
-
-    void changeSize(int newSize) { notifySizeChanged(newSize); }
-};
-
-// Other tests may have created a renderthread and EGL context.
-// This will destroy the EGLContext on RenderThread if it exists so that the
-// current thread can spoof being a GPU thread
-static void destroyEglContext() {
-    if (TestUtils::isRenderThreadRunning()) {
-        TestUtils::runOnRenderThread([](RenderThread& thread) { thread.destroyRenderingContext(); });
-    }
-}
-
-TEST(GpuMemoryTracker, sizeCheck) {
-    destroyEglContext();
-
-    GpuMemoryTracker::onGpuContextCreated();
-    ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-    ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
-    {
-        TestGPUObject myObj;
-        ASSERT_EQ(1, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
-        myObj.changeSize(500);
-        ASSERT_EQ(500, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-        myObj.changeSize(1000);
-        ASSERT_EQ(1000, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-        myObj.changeSize(300);
-        ASSERT_EQ(300, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-    }
-    ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
-    ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
-    GpuMemoryTracker::onGpuContextDestroyed();
-}
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 8293b5f..cb132f5 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.util.SparseIntArray;
 
 import java.lang.annotation.Retention;
@@ -127,6 +128,23 @@
      * A device type describing a Hearing Aid.
      */
     public static final int TYPE_HEARING_AID   = 23;
+    /**
+     * A device type describing the speaker system (i.e. a mono speaker or stereo speakers) built
+     * in a device, that is specifically tuned for outputting sounds like notifications and alarms
+     * (i.e. sounds the user couldn't necessarily anticipate).
+     * <p>Note that this physical audio device may be the same as {@link #TYPE_BUILTIN_SPEAKER}
+     * but is driven differently to safely accommodate the different use case.</p>
+     */
+    public static final int TYPE_BUILTIN_SPEAKER_SAFE = 24;
+    /**
+     * @hide
+     * A device type for rerouting audio within the Android framework between mixes and
+     * system applications. Typically created when using
+     * {@link android.media.audiopolicy.AudioPolicy} for mixes created with the
+     * {@link android.media.audiopolicy.AudioMix#ROUTE_FLAG_RENDER} flag.
+     */
+    @SystemApi
+    public static final int TYPE_REMOTE_SUBMIX = 25;
 
     /** @hide */
     @IntDef(flag = false, prefix = "TYPE", value = {
@@ -228,6 +246,8 @@
             case TYPE_IP:
             case TYPE_BUS:
             case TYPE_HEARING_AID:
+            case TYPE_BUILTIN_SPEAKER_SAFE:
+            case TYPE_REMOTE_SUBMIX:
                 return true;
             default:
                 return false;
@@ -253,6 +273,7 @@
             case TYPE_LINE_DIGITAL:
             case TYPE_IP:
             case TYPE_BUS:
+            case TYPE_REMOTE_SUBMIX:
                 return true;
             default:
                 return false;
@@ -449,6 +470,9 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_IP, TYPE_IP);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BUS, TYPE_BUS);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_HEARING_AID, TYPE_HEARING_AID);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_SPEAKER_SAFE,
+                TYPE_BUILTIN_SPEAKER_SAFE);
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
 
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO);
@@ -468,10 +492,7 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, TYPE_BLUETOOTH_A2DP);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_IP, TYPE_IP);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUS, TYPE_BUS);
-
-        // not covered here, legacy
-        //AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
-        //AudioSystem.DEVICE_IN_REMOTE_SUBMIX
+        INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
 
         // privileges mapping to output device
         EXT_TO_INT_DEVICE_MAPPING = new SparseIntArray();
@@ -498,6 +519,9 @@
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_IP, AudioSystem.DEVICE_OUT_IP);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUS, AudioSystem.DEVICE_OUT_BUS);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_HEARING_AID, AudioSystem.DEVICE_OUT_HEARING_AID);
+        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUILTIN_SPEAKER_SAFE,
+                AudioSystem.DEVICE_OUT_SPEAKER_SAFE);
+        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_OUT_REMOTE_SUBMIX);
     }
 }
 
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e410882..1b870e8 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4331,6 +4331,26 @@
         }
     }
 
+    /**
+     * @hide
+     * Get the audio devices that would be used for the routing of the given audio attributes.
+     * @param attributes the {@link AudioAttributes} for which the routing is being queried
+     * @return an empty list if there was an issue with the request, a list of audio devices
+     *   otherwise (typically one device, except for duplicated paths).
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public @NonNull List<AudioDeviceAddress> getDevicesForAttributes(
+            @NonNull AudioAttributes attributes) {
+        Objects.requireNonNull(attributes);
+        final IAudioService service = getService();
+        try {
+            return service.getDevicesForAttributes(attributes);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
      /**
      * Indicate wired accessory connection state change.
      * @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx)
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index e584add..fe57e71 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -33,6 +33,7 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 /* IF YOU CHANGE ANY OF THE CONSTANTS IN THIS FILE, DO NOT FORGET
@@ -1077,6 +1078,41 @@
     @UnsupportedAppUsage
     public static native int getDevicesForStream(int stream);
 
+    /**
+     * Do not use directly, see {@link AudioManager#getDevicesForAttributes(AudioAttributes)}
+     * Get the audio devices that would be used for the routing of the given audio attributes.
+     * @param attributes the {@link AudioAttributes} for which the routing is being queried
+     * @return an empty list if there was an issue with the request, a list of audio devices
+     *   otherwise (typically one device, except for duplicated paths).
+     */
+    public static @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes(
+            @NonNull AudioAttributes attributes) {
+        Objects.requireNonNull(attributes);
+        final AudioDeviceAddress[] devices = new AudioDeviceAddress[MAX_DEVICE_ROUTING];
+        final int res = getDevicesForAttributes(attributes, devices);
+        final ArrayList<AudioDeviceAddress> routeDevices = new ArrayList<>();
+        if (res != SUCCESS) {
+            Log.e(TAG, "error " + res + " in getDevicesForAttributes for " + attributes);
+            return routeDevices;
+        }
+
+        for (AudioDeviceAddress device : devices) {
+            if (device != null) {
+                routeDevices.add(device);
+            }
+        }
+        return routeDevices;
+    }
+
+    /**
+     * Maximum number of audio devices a track is ever routed to, determines the size of the
+     * array passed to {@link #getDevicesForAttributes(AudioAttributes, AudioDeviceAddress[])}
+     */
+    private static final int MAX_DEVICE_ROUTING = 4;
+
+    private static native int getDevicesForAttributes(@NonNull AudioAttributes aa,
+                                                      @NonNull AudioDeviceAddress[] devices);
+
     /** @hide returns true if master mono is enabled. */
     public static native boolean getMasterMono();
     /** @hide enables or disables the master mono mode. */
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index ad7335e..8e8385d 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -272,6 +272,8 @@
 
     AudioDeviceAddress getPreferredDeviceForStrategy(in int strategy);
 
+    List<AudioDeviceAddress> getDevicesForAttributes(in AudioAttributes attributes);
+
     // WARNING: read warning at top of file, new methods that need to be used by native
     // code via IAudioManager.h need to be added to the top section.
 }
diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl
index 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/RouteDiscoveryRequest.aidl b/media/java/android/media/RouteDiscoveryPreference.aidl
similarity index 94%
rename from media/java/android/media/RouteDiscoveryRequest.aidl
rename to media/java/android/media/RouteDiscoveryPreference.aidl
index 744f656..898eb39 100644
--- a/media/java/android/media/RouteDiscoveryRequest.aidl
+++ b/media/java/android/media/RouteDiscoveryPreference.aidl
@@ -16,4 +16,4 @@
 
 package android.media;
 
-parcelable RouteDiscoveryRequest;
+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.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/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index aff7257..aece39d 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -940,16 +940,15 @@
         /**
          * Called when a media key event is dispatched through the media session service. The
          * session token can be {@link null} if the framework has sent the media key event to the
-         * media button receiver to revive the media app's playback.
-         *
-         * the session is dead when , but the framework sent
+         * media button receiver to revive the media app's playback after the corresponding session
+         * is released.
          *
          * @param event Dispatched media key event.
          * @param packageName Package
          * @param sessionToken The media session's token. Can be {@code null}.
          */
         default void onMediaKeyEventDispatched(@NonNull KeyEvent event, @NonNull String packageName,
-                @NonNull MediaSession.Token sessionToken) { }
+                @Nullable MediaSession.Token sessionToken) { }
     }
 
     /**
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 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/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 1c38301..938ffcd 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -428,8 +428,7 @@
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
     public int setParameter(@Nullable UUID soundModelId,
-            @ModelParams int modelParam, int value)
-            throws UnsupportedOperationException, IllegalArgumentException {
+            @ModelParams int modelParam, int value) {
         try {
             return mSoundTriggerService.setParameter(new ParcelUuid(soundModelId), modelParam,
                     value);
@@ -449,15 +448,10 @@
      * @param soundModelId UUID of model to get parameter
      * @param modelParam   {@link ModelParams}
      * @return value of parameter
-     * @throws UnsupportedOperationException if hal or model do not support this API.
-     *         {@link SoundTriggerManager#queryParameter} should be checked first.
-     * @throws IllegalArgumentException if invalid model handle or parameter is passed.
-     *         {@link SoundTriggerManager#queryParameter} should be checked first.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
     public int getParameter(@NonNull UUID soundModelId,
-            @ModelParams int modelParam)
-            throws UnsupportedOperationException, IllegalArgumentException {
+            @ModelParams int modelParam) {
         try {
             return mSoundTriggerService.getParameter(new ParcelUuid(soundModelId), modelParam);
         } catch (RemoteException e) {
@@ -479,8 +473,7 @@
     public ModelParamRange queryParameter(@Nullable UUID soundModelId,
             @ModelParams int modelParam) {
         try {
-            return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId),
-                    modelParam);
+            return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId), modelParam);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl b/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl
new file mode 100644
index 0000000..97a8849
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.soundtrigger_middleware;
+
+/**
+ * AudioCapabilities supported by the implemented HAL driver.
+ * @hide
+ */
+@Backing(type="int")
+enum AudioCapabilities {
+    /**
+     * If set the underlying module supports AEC.
+     */
+    ECHO_CANCELLATION = 1 << 0,
+    /**
+     * If set, the underlying module supports noise suppression.
+     */
+    NOISE_SUPPRESSION = 1 << 1,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
index c7642e8..5c0eeb1 100644
--- a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
@@ -28,6 +28,12 @@
     /* Configuration for each key phrase. */
     PhraseRecognitionExtra[] phraseRecognitionExtras;
 
+    /**
+     * Bit field encoding of the AudioCapabilities
+     * supported by the firmware.
+     */
+    int audioCapabilities;
+
     /** Opaque capture configuration data. */
     byte[] data;
 }
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
index 1a3b402..9c56e7b 100644
--- a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
@@ -30,6 +30,14 @@
      * Unique implementation ID. The UUID must change with each version of
        the engine implementation */
     String     uuid;
+    /**
+     * String naming the architecture used for running the supported models.
+     * (eg. a platform running models on a DSP could implement this string to convey the DSP
+     * architecture used)
+     * This property is supported for soundtrigger HAL v2.3 and above.
+     * If running a previous version, the string will be empty.
+     */
+    String supportedModelArch;
     /** Maximum number of concurrent sound models loaded */
     int maxSoundModels;
     /** Maximum number of key phrases */
@@ -50,4 +58,11 @@
      * Rated power consumption when detection is active with TDB
      * silence/sound/speech ratio */
     int powerConsumptionMw;
+    /**
+     * Bit field encoding of the AudioCapabilities
+     * supported by the firmware.
+     * This property is supported for soundtrigger HAL v2.3 and above.
+     * If running a previous version, this value will be 0.
+     */
+    int audioCapabilities;
 }
diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/java/android/media/soundtrigger_middleware/Status.aidl
index d8f9d8f..5d082e2 100644
--- a/media/java/android/media/soundtrigger_middleware/Status.aidl
+++ b/media/java/android/media/soundtrigger_middleware/Status.aidl
@@ -26,4 +26,6 @@
     RESOURCE_CONTENTION = 1,
     /** Operation is not supported in this implementation. This is a permanent condition. */
     OPERATION_NOT_SUPPORTED = 2,
+    /** Temporary lack of permission. */
+    TEMPORARY_PERMISSION_DENIED = 3,
 }
diff --git a/media/java/android/media/tv/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/DemuxCapabilities.java b/media/java/android/media/tv/tuner/DemuxCapabilities.java
index bda166e..83abf86 100644
--- a/media/java/android/media/tv/tuner/DemuxCapabilities.java
+++ b/media/java/android/media/tv/tuner/DemuxCapabilities.java
@@ -16,11 +16,32 @@
 
 package android.media.tv.tuner;
 
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.media.tv.tuner.filter.FilterConfiguration;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Capabilities info for Demux.
+ *
  * @hide
  */
 public class DemuxCapabilities {
+
+    /** @hide */
+    @IntDef(flag = true, value = {
+            FilterConfiguration.FILTER_TYPE_TS,
+            FilterConfiguration.FILTER_TYPE_MMTP,
+            FilterConfiguration.FILTER_TYPE_IP,
+            FilterConfiguration.FILTER_TYPE_TLV,
+            FilterConfiguration.FILTER_TYPE_ALP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FilterCapabilities {}
+
     private final int mNumDemux;
     private final int mNumRecord;
     private final int mNumPlayback;
@@ -34,7 +55,8 @@
     private final int mFilterCaps;
     private final int[] mLinkCaps;
 
-    DemuxCapabilities(int numDemux, int numRecord, int numPlayback, int numTsFilter,
+    // Used by JNI
+    private DemuxCapabilities(int numDemux, int numRecord, int numPlayback, int numTsFilter,
             int numSectionFilter, int numAudioFilter, int numVideoFilter, int numPesFilter,
             int numPcrFilter, int numBytesInSectionFilter, int filterCaps, int[] linkCaps) {
         mNumDemux = numDemux;
@@ -51,52 +73,73 @@
         mLinkCaps = linkCaps;
     }
 
-    /** Gets total number of demuxes. */
+    /**
+     * Gets total number of demuxes.
+     */
     public int getNumDemux() {
         return mNumDemux;
     }
-    /** Gets max number of recordings at a time. */
+    /**
+     * Gets max number of recordings at a time.
+     */
     public int getNumRecord() {
         return mNumRecord;
     }
-    /** Gets max number of playbacks at a time. */
+    /**
+     * Gets max number of playbacks at a time.
+     */
     public int getNumPlayback() {
         return mNumPlayback;
     }
-    /** Gets number of TS filters. */
+    /**
+     * Gets number of TS filters.
+     */
     public int getNumTsFilter() {
         return mNumTsFilter;
     }
-    /** Gets number of section filters. */
+    /**
+     * Gets number of section filters.
+     */
     public int getNumSectionFilter() {
         return mNumSectionFilter;
     }
-    /** Gets number of audio filters. */
+    /**
+     * Gets number of audio filters.
+     */
     public int getNumAudioFilter() {
         return mNumAudioFilter;
     }
-    /** Gets number of video filters. */
+    /**
+     * Gets number of video filters.
+     */
     public int getNumVideoFilter() {
         return mNumVideoFilter;
     }
-    /** Gets number of PES filters. */
+    /**
+     * Gets number of PES filters.
+     */
     public int getNumPesFilter() {
         return mNumPesFilter;
     }
-    /** Gets number of PCR filters. */
+    /**
+     * Gets number of PCR filters.
+     */
     public int getNumPcrFilter() {
         return mNumPcrFilter;
     }
-    /** Gets number of bytes in the mask of a section filter. */
+    /**
+     * Gets number of bytes in the mask of a section filter.
+     */
     public int getNumBytesInSectionFilter() {
         return mNumBytesInSectionFilter;
     }
     /**
      * Gets filter capabilities in bit field.
      *
-     * The bits of the returned value is corresponding to the types in
-     * {@link TunerConstants.FilterType}.
+     * <p>The bits of the returned value is corresponding to the types in
+     * {@link FilterConfiguration}.
      */
+    @FilterCapabilities
     public int getFilterCapabilities() {
         return mFilterCaps;
     }
@@ -104,10 +147,12 @@
     /**
      * Gets link capabilities.
      *
-     * The returned array contains the same elements as the number of types in
-     * {@link TunerConstants.FilterType}.
-     * The ith element represents the filter's capability as the source for the ith type
+     * <p>The returned array contains the same elements as the number of types in
+     * {@link FilterConfiguration}.
+     * <p>The ith element represents the filter's capability as the source for the ith type.
      */
+    @Nullable
+    @Size(5)
     public int[] getLinkCapabilities() {
         return mLinkCaps;
     }
diff --git a/media/java/android/media/tv/tuner/Descrambler.java b/media/java/android/media/tv/tuner/Descrambler.java
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/Dvr.java b/media/java/android/media/tv/tuner/Dvr.java
deleted file mode 100644
index 0bfba8f..0000000
--- a/media/java/android/media/tv/tuner/Dvr.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.tv.tuner;
-
-import android.annotation.NonNull;
-import android.media.tv.tuner.Tuner.DvrCallback;
-import android.media.tv.tuner.Tuner.Filter;
-import android.os.ParcelFileDescriptor;
-
-/** @hide */
-public class Dvr {
-    private long mNativeContext;
-    private DvrCallback mCallback;
-
-    private native int nativeAttachFilter(Filter filter);
-    private native int nativeDetachFilter(Filter filter);
-    private native int nativeConfigureDvr(DvrSettings settings);
-    private native int nativeStartDvr();
-    private native int nativeStopDvr();
-    private native int nativeFlushDvr();
-    private native int nativeClose();
-    private native void nativeSetFileDescriptor(int fd);
-    private native int nativeRead(int size);
-    private native int nativeRead(byte[] bytes, int offset, int size);
-    private native int nativeWrite(int size);
-    private native int nativeWrite(byte[] bytes, int offset, int size);
-
-    private Dvr() {}
-
-    /**
-     * Attaches a filter to DVR interface for recording.
-     *
-     * @param filter the filter to be attached.
-     * @return result status of the operation.
-     */
-    public int attachFilter(Filter filter) {
-        return nativeAttachFilter(filter);
-    }
-
-    /**
-     * Detaches a filter from DVR interface.
-     *
-     * @param filter the filter to be detached.
-     * @return result status of the operation.
-     */
-    public int detachFilter(Filter filter) {
-        return nativeDetachFilter(filter);
-    }
-
-    /**
-     * Configures the DVR.
-     *
-     * @param settings the settings of the DVR interface.
-     * @return result status of the operation.
-     */
-    public int configure(DvrSettings settings) {
-        return nativeConfigureDvr(settings);
-    }
-
-    /**
-     * Starts DVR.
-     *
-     * Starts consuming playback data or producing data for recording.
-     *
-     * @return result status of the operation.
-     */
-    public int start() {
-        return nativeStartDvr();
-    }
-
-    /**
-     * Stops DVR.
-     *
-     * Stops consuming playback data or producing data for recording.
-     *
-     * @return result status of the operation.
-     */
-    public int stop() {
-        return nativeStopDvr();
-    }
-
-    /**
-     * Flushed DVR data.
-     *
-     * @return result status of the operation.
-     */
-    public int flush() {
-        return nativeFlushDvr();
-    }
-
-    /**
-     * closes the DVR instance to release resources.
-     *
-     * @return result status of the operation.
-     */
-    public int close() {
-        return nativeClose();
-    }
-
-    /**
-     * Sets file descriptor to read/write data.
-     */
-    public void setFileDescriptor(ParcelFileDescriptor fd) {
-        nativeSetFileDescriptor(fd.getFd());
-    }
-
-    /**
-     * Reads data from the file for DVR playback.
-     */
-    public int read(int size) {
-        return nativeRead(size);
-    }
-
-    /**
-     * Reads data from the buffer for DVR playback.
-     */
-    public int read(@NonNull byte[] bytes, int offset, int size) {
-        if (size + offset > bytes.length) {
-            throw new ArrayIndexOutOfBoundsException(
-                    "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size);
-        }
-        return nativeRead(bytes, offset, size);
-    }
-
-    /**
-     * Writes recording data to file.
-     */
-    public int write(int size) {
-        return nativeWrite(size);
-    }
-
-    /**
-     * Writes recording data to buffer.
-     */
-    public int write(@NonNull byte[] bytes, int offset, int size) {
-        return nativeWrite(bytes, offset, size);
-    }
-}
diff --git a/media/java/android/media/tv/tuner/DvrSettings.java b/media/java/android/media/tv/tuner/DvrSettings.java
deleted file mode 100644
index 76160dc..0000000
--- a/media/java/android/media/tv/tuner/DvrSettings.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.tv.tuner;
-
-import android.media.tv.tuner.TunerConstants.DataFormat;
-import android.media.tv.tuner.TunerConstants.DvrSettingsType;
-
-/**
- * DVR settings.
- *
- * @hide
- */
-public class DvrSettings {
-    private int mStatusMask;
-    private int mLowThreshold;
-    private int mHighThreshold;
-    private int mPacketSize;
-
-    @DataFormat
-    private int mDataFormat;
-    @DvrSettingsType
-    private int mType;
-
-    private DvrSettings(int statusMask, int lowThreshold, int highThreshold, int packetSize,
-            @DataFormat int dataFormat, @DvrSettingsType int type) {
-        mStatusMask = statusMask;
-        mLowThreshold = lowThreshold;
-        mHighThreshold = highThreshold;
-        mPacketSize = packetSize;
-        mDataFormat = dataFormat;
-        mType = type;
-    }
-
-    /**
-     * Creates a new builder.
-     */
-    public static Builder newBuilder() {
-        return new Builder();
-    }
-
-    /**
-     * Builder for DvrSettings.
-     */
-    public static final class Builder {
-        private int mStatusMask;
-        private int mLowThreshold;
-        private int mHighThreshold;
-        private int mPacketSize;
-        @DataFormat
-        private int mDataFormat;
-        @DvrSettingsType
-        private int mType;
-
-        /**
-         * Sets status mask.
-         */
-        public Builder setStatusMask(int statusMask) {
-            this.mStatusMask = statusMask;
-            return this;
-        }
-
-        /**
-         * Sets low threshold.
-         */
-        public Builder setLowThreshold(int lowThreshold) {
-            this.mLowThreshold = lowThreshold;
-            return this;
-        }
-
-        /**
-         * Sets high threshold.
-         */
-        public Builder setHighThreshold(int highThreshold) {
-            this.mHighThreshold = highThreshold;
-            return this;
-        }
-
-        /**
-         * Sets packet size.
-         */
-        public Builder setPacketSize(int packetSize) {
-            this.mPacketSize = packetSize;
-            return this;
-        }
-
-        /**
-         * Sets data format.
-         */
-        public Builder setDataFormat(@DataFormat int dataFormat) {
-            this.mDataFormat = dataFormat;
-            return this;
-        }
-
-        /**
-         * Sets settings type.
-         */
-        public Builder setType(@DvrSettingsType int type) {
-            this.mType = type;
-            return this;
-        }
-
-        /**
-         * Builds a DvrSettings instance.
-         */
-        public DvrSettings build() {
-            return new DvrSettings(
-                    mStatusMask, mLowThreshold, mHighThreshold, mPacketSize, mDataFormat, mType);
-        }
-    }
-}
diff --git a/media/java/android/media/tv/tuner/FrontendSettings.java b/media/java/android/media/tv/tuner/FrontendSettings.java
index e2e9910..ad8422c 100644
--- a/media/java/android/media/tv/tuner/FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/FrontendSettings.java
@@ -17,7 +17,6 @@
 package android.media.tv.tuner;
 
 import android.annotation.SystemApi;
-import android.media.tv.tuner.TunerConstants.FrontendSettingsType;
 
 /**
  * Frontend settings for tune and scan operations.
@@ -35,7 +34,6 @@
     /**
      * Returns the frontend type.
      */
-    @FrontendSettingsType
     public abstract int getType();
 
     /**
diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java
new file mode 100644
index 0000000..c7cc9e6d
--- /dev/null
+++ b/media/java/android/media/tv/tuner/Lnb.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.Tuner.LnbCallback;
+import android.media.tv.tuner.TunerConstants.Result;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * LNB (low-noise block downconverter) for satellite tuner.
+ *
+ * A Tuner LNB (low-noise block downconverter) is used by satellite frontend to receive the
+ * microwave signal from the satellite, amplify it, and downconvert the frequency to a lower
+ * frequency.
+ *
+ * @hide
+ */
+@SystemApi
+public class Lnb implements AutoCloseable {
+    /** @hide */
+    @IntDef({VOLTAGE_NONE, VOLTAGE_5V, VOLTAGE_11V, VOLTAGE_12V, VOLTAGE_13V, VOLTAGE_14V,
+            VOLTAGE_15V, VOLTAGE_18V, VOLTAGE_19V})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Voltage {}
+
+    /**
+     * LNB power voltage not set.
+     */
+    public static final int VOLTAGE_NONE = Constants.LnbVoltage.NONE;
+    /**
+     * LNB power voltage 5V.
+     */
+    public static final int VOLTAGE_5V = Constants.LnbVoltage.VOLTAGE_5V;
+    /**
+     * LNB power voltage 11V.
+     */
+    public static final int VOLTAGE_11V = Constants.LnbVoltage.VOLTAGE_11V;
+    /**
+     * LNB power voltage 12V.
+     */
+    public static final int VOLTAGE_12V = Constants.LnbVoltage.VOLTAGE_12V;
+    /**
+     * LNB power voltage 13V.
+     */
+    public static final int VOLTAGE_13V = Constants.LnbVoltage.VOLTAGE_13V;
+    /**
+     * LNB power voltage 14V.
+     */
+    public static final int VOLTAGE_14V = Constants.LnbVoltage.VOLTAGE_14V;
+    /**
+     * LNB power voltage 15V.
+     */
+    public static final int VOLTAGE_15V = Constants.LnbVoltage.VOLTAGE_15V;
+    /**
+     * LNB power voltage 18V.
+     */
+    public static final int VOLTAGE_18V = Constants.LnbVoltage.VOLTAGE_18V;
+    /**
+     * LNB power voltage 19V.
+     */
+    public static final int VOLTAGE_19V = Constants.LnbVoltage.VOLTAGE_19V;
+
+    /** @hide */
+    @IntDef({TONE_NONE, TONE_CONTINUOUS})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Tone {}
+
+    /**
+     * LNB tone mode not set.
+     */
+    public static final int TONE_NONE = Constants.LnbTone.NONE;
+    /**
+     * LNB continuous tone mode.
+     */
+    public static final int TONE_CONTINUOUS = Constants.LnbTone.CONTINUOUS;
+
+    /** @hide */
+    @IntDef({POSITION_UNDEFINED, POSITION_A, POSITION_B})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Position {}
+
+    /**
+     * LNB position is not defined.
+     */
+    public static final int POSITION_UNDEFINED = Constants.LnbPosition.UNDEFINED;
+    /**
+     * Position A of two-band LNBs
+     */
+    public static final int POSITION_A = Constants.LnbPosition.POSITION_A;
+    /**
+     * Position B of two-band LNBs
+     */
+    public static final int POSITION_B = Constants.LnbPosition.POSITION_B;
+
+    int mId;
+    LnbCallback mCallback;
+    Context mContext;
+
+    private native int nativeSetVoltage(int voltage);
+    private native int nativeSetTone(int tone);
+    private native int nativeSetSatellitePosition(int position);
+    private native int nativeSendDiseqcMessage(byte[] message);
+    private native int nativeClose();
+
+    Lnb(int id) {
+        mId = id;
+    }
+
+    /** @hide */
+    public void setCallback(@Nullable LnbCallback callback) {
+        mCallback = callback;
+        if (mCallback == null) {
+            return;
+        }
+    }
+
+    /**
+     * Sets the LNB's power voltage.
+     *
+     * @param voltage the power voltage constant the Lnb to use.
+     * @return result status of the operation.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Result
+    public int setVoltage(@Voltage int voltage) {
+        TunerUtils.checkTunerPermission(mContext);
+        return nativeSetVoltage(voltage);
+    }
+
+    /**
+     * Sets the LNB's tone mode.
+     *
+     * @param tone the tone mode the Lnb to use.
+     * @return result status of the operation.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Result
+    public int setTone(@Tone int tone) {
+        TunerUtils.checkTunerPermission(mContext);
+        return nativeSetTone(tone);
+    }
+
+    /**
+     * Selects the LNB's position.
+     *
+     * @param position the position the Lnb to use.
+     * @return result status of the operation.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Result
+    public int setSatellitePosition(@Position int position) {
+        TunerUtils.checkTunerPermission(mContext);
+        return nativeSetSatellitePosition(position);
+    }
+
+    /**
+     * Sends DiSEqC (Digital Satellite Equipment Control) message.
+     *
+     * The response message from the device comes back through callback onDiseqcMessage.
+     *
+     * @param message a byte array of data for DiSEqC message which is specified by EUTELSAT Bus
+     *         Functional Specification Version 4.2.
+     *
+     * @return result status of the operation.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Result
+    public int sendDiseqcMessage(@NonNull byte[] message) {
+        TunerUtils.checkTunerPermission(mContext);
+        return nativeSendDiseqcMessage(message);
+    }
+
+    /**
+     * Releases the LNB instance.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    public void close() {
+        TunerUtils.checkTunerPermission(mContext);
+        nativeClose();
+    }
+}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 962a7f6..862489f 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -24,12 +24,11 @@
 import android.media.tv.tuner.TunerConstants.FilterStatus;
 import android.media.tv.tuner.TunerConstants.FilterSubtype;
 import android.media.tv.tuner.TunerConstants.FrontendScanType;
-import android.media.tv.tuner.TunerConstants.LnbPosition;
-import android.media.tv.tuner.TunerConstants.LnbTone;
-import android.media.tv.tuner.TunerConstants.LnbVoltage;
 import android.media.tv.tuner.TunerConstants.Result;
+import android.media.tv.tuner.dvr.Dvr;
 import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
 import android.media.tv.tuner.filter.FilterEvent;
+import android.media.tv.tuner.filter.TimeFilter;
 import android.media.tv.tuner.frontend.FrontendCallback;
 import android.media.tv.tuner.frontend.FrontendInfo;
 import android.media.tv.tuner.frontend.FrontendStatus;
@@ -219,11 +218,6 @@
                     }
                     break;
                 }
-                case MSG_ON_LNB_EVENT: {
-                    if (mLnb != null && mLnb.mCallback != null) {
-                        mLnb.mCallback.onEvent(msg.arg1);
-                    }
-                }
                 default:
                     // fall through
             }
@@ -423,8 +417,14 @@
         return mFrontend.mId;
     }
 
-    /** @hide */
-    private static DemuxCapabilities getDemuxCapabilities() {
+    /**
+     * Gets Demux capabilities.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    @Nullable
+    public static DemuxCapabilities getDemuxCapabilities(@NonNull Context context) {
+        TunerUtils.checkTunerPermission(context);
         return nativeGetDemuxCapabilities();
     }
 
@@ -474,102 +474,6 @@
         return filter;
     }
 
-    /**
-     * Open a time filter instance.
-     *
-     * It is used to open time filter of demux.
-     *
-     * @return a time filter instance.
-     * @hide
-     */
-    public TimeFilter openTimeFilter() {
-        return nativeOpenTimeFilter();
-    }
-
-    /** @hide */
-    public class Lnb {
-        private int mId;
-        private LnbCallback mCallback;
-
-        private native int nativeSetVoltage(int voltage);
-        private native int nativeSetTone(int tone);
-        private native int nativeSetSatellitePosition(int position);
-        private native int nativeSendDiseqcMessage(byte[] message);
-        private native int nativeClose();
-
-        private Lnb(int id) {
-            mId = id;
-        }
-
-        public void setCallback(@Nullable LnbCallback callback) {
-            mCallback = callback;
-            if (mCallback == null) {
-                return;
-            }
-            if (mHandler == null) {
-                mHandler = createEventHandler();
-            }
-        }
-
-        /**
-         * Sets the LNB's power voltage.
-         *
-         * @param voltage the power voltage the Lnb to use.
-         * @return result status of the operation.
-         */
-        @Result
-        public int setVoltage(@LnbVoltage int voltage) {
-            return nativeSetVoltage(voltage);
-        }
-
-        /**
-         * Sets the LNB's tone mode.
-         *
-         * @param tone the tone mode the Lnb to use.
-         * @return result status of the operation.
-         */
-        @Result
-        public int setTone(@LnbTone int tone) {
-            return nativeSetTone(tone);
-        }
-
-        /**
-         * Selects the LNB's position.
-         *
-         * @param position the position the Lnb to use.
-         * @return result status of the operation.
-         */
-        @Result
-        public int setSatellitePosition(@LnbPosition int position) {
-            return nativeSetSatellitePosition(position);
-        }
-
-        /**
-         * Sends DiSEqC (Digital Satellite Equipment Control) message.
-         *
-         * The response message from the device comes back through callback onDiseqcMessage.
-         *
-         * @param message a byte array of data for DiSEqC message which is specified by EUTELSAT Bus
-         *         Functional Specification Version 4.2.
-         *
-         * @return result status of the operation.
-         */
-        @Result
-        public int sendDiseqcMessage(byte[] message) {
-            return nativeSendDiseqcMessage(message);
-        }
-
-        /**
-         * Releases the LNB instance
-         *
-         * @return result status of the operation.
-         */
-        @Result
-        public int close() {
-            return nativeClose();
-        }
-    }
-
     private List<Integer> getLnbIds() {
         mLnbIds = nativeGetLnbIds();
         return mLnbIds;
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index bbaa518..c2d6c58c 100644
--- a/media/java/android/media/tv/tuner/TunerConstants.java
+++ b/media/java/android/media/tv/tuner/TunerConstants.java
@@ -38,35 +38,6 @@
 
 
     /** @hide */
-    @IntDef({FRONTEND_TYPE_UNDEFINED, FRONTEND_TYPE_ANALOG, FRONTEND_TYPE_ATSC, FRONTEND_TYPE_ATSC3,
-            FRONTEND_TYPE_DVBC, FRONTEND_TYPE_DVBS, FRONTEND_TYPE_DVBT, FRONTEND_TYPE_ISDBS,
-            FRONTEND_TYPE_ISDBS3, FRONTEND_TYPE_ISDBT})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendType {}
-
-    /** @hide */
-    public static final int FRONTEND_TYPE_UNDEFINED = Constants.FrontendType.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ANALOG = Constants.FrontendType.ANALOG;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ATSC = Constants.FrontendType.ATSC;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ATSC3 = Constants.FrontendType.ATSC3;
-    /** @hide */
-    public static final int FRONTEND_TYPE_DVBC = Constants.FrontendType.DVBC;
-    /** @hide */
-    public static final int FRONTEND_TYPE_DVBS = Constants.FrontendType.DVBS;
-    /** @hide */
-    public static final int FRONTEND_TYPE_DVBT = Constants.FrontendType.DVBT;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ISDBS = Constants.FrontendType.ISDBS;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ISDBS3 = Constants.FrontendType.ISDBS3;
-    /** @hide */
-    public static final int FRONTEND_TYPE_ISDBT = Constants.FrontendType.ISDBT;
-
-
-    /** @hide */
     @IntDef({FRONTEND_EVENT_TYPE_LOCKED, FRONTEND_EVENT_TYPE_NO_SIGNAL,
             FRONTEND_EVENT_TYPE_LOST_LOCK})
     @Retention(RetentionPolicy.SOURCE)
@@ -80,54 +51,6 @@
 
 
     /** @hide */
-    @IntDef({DATA_FORMAT_TS, DATA_FORMAT_PES, DATA_FORMAT_ES, DATA_FORMAT_SHV_TLV})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface DataFormat {}
-    /** @hide */
-    public static final int DATA_FORMAT_TS = Constants.DataFormat.TS;
-    /** @hide */
-    public static final int DATA_FORMAT_PES = Constants.DataFormat.PES;
-    /** @hide */
-    public static final int DATA_FORMAT_ES = Constants.DataFormat.ES;
-    /** @hide */
-    public static final int DATA_FORMAT_SHV_TLV = Constants.DataFormat.SHV_TLV;
-
-
-    /** @hide */
-    @IntDef({DEMUX_T_PID, DEMUX_MMPT_PID})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface DemuxPidType {}
-    /** @hide */
-    public static final int DEMUX_T_PID = 1;
-    /** @hide */
-    public static final int DEMUX_MMPT_PID = 2;
-
-    /** @hide */
-    @IntDef({FRONTEND_SETTINGS_ANALOG, FRONTEND_SETTINGS_ATSC, FRONTEND_SETTINGS_ATSC3,
-            FRONTEND_SETTINGS_DVBS, FRONTEND_SETTINGS_DVBC, FRONTEND_SETTINGS_DVBT,
-            FRONTEND_SETTINGS_ISDBS, FRONTEND_SETTINGS_ISDBS3, FRONTEND_SETTINGS_ISDBT})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendSettingsType {}
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ANALOG = 1;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ATSC = 2;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ATSC3 = 3;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_DVBS = 4;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_DVBC = 5;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_DVBT = 6;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ISDBS = 7;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ISDBS3 = 8;
-    /** @hide */
-    public static final int FRONTEND_SETTINGS_ISDBT = 9;
-
-    /** @hide */
     @IntDef({FILTER_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,
@@ -171,8 +94,8 @@
     public static final int FILTER_SUBTYPE_PTP = 16;
 
     /** @hide */
-    @IntDef({FILTER_STATUS_DATA_READY, FILTER_STATUS_LOW_WATER, FILTER_STATUS_HIGH_WATER,
-            FILTER_STATUS_OVERFLOW})
+    @IntDef(flag = true, prefix = "FILTER_STATUS_", value = {FILTER_STATUS_DATA_READY,
+            FILTER_STATUS_LOW_WATER, FILTER_STATUS_HIGH_WATER, FILTER_STATUS_OVERFLOW})
     @Retention(RetentionPolicy.SOURCE)
     public @interface FilterStatus {}
 
@@ -201,6 +124,187 @@
      */
     public static final int FILTER_STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW;
 
+    /**
+     * Indexes can be tagged through TS (Transport Stream) header.
+     *
+     * @hide
+     */
+    @IntDef(flag = true, value = {TS_INDEX_FIRST_PACKET, TS_INDEX_PAYLOAD_UNIT_START_INDICATOR,
+            TS_INDEX_CHANGE_TO_NOT_SCRAMBLED, TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED,
+            TS_INDEX_CHANGE_TO_ODD_SCRAMBLED, TS_INDEX_DISCONTINUITY_INDICATOR,
+            TS_INDEX_RANDOM_ACCESS_INDICATOR, TS_INDEX_PRIORITY_INDICATOR, TS_INDEX_PCR_FLAG,
+            TS_INDEX_OPCR_FLAG, TS_INDEX_SPLICING_POINT_FLAG, TS_INDEX_PRIVATE_DATA,
+            TS_INDEX_ADAPTATION_EXTENSION_FLAG})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TsIndex {}
+
+    /**
+     * TS index FIRST_PACKET.
+     * @hide
+     */
+    public static final int TS_INDEX_FIRST_PACKET = Constants.DemuxTsIndex.FIRST_PACKET;
+    /**
+     * TS index PAYLOAD_UNIT_START_INDICATOR.
+     * @hide
+     */
+    public static final int TS_INDEX_PAYLOAD_UNIT_START_INDICATOR =
+            Constants.DemuxTsIndex.PAYLOAD_UNIT_START_INDICATOR;
+    /**
+     * TS index CHANGE_TO_NOT_SCRAMBLED.
+     * @hide
+     */
+    public static final int TS_INDEX_CHANGE_TO_NOT_SCRAMBLED =
+            Constants.DemuxTsIndex.CHANGE_TO_NOT_SCRAMBLED;
+    /**
+     * TS index CHANGE_TO_EVEN_SCRAMBLED.
+     * @hide
+     */
+    public static final int TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED =
+            Constants.DemuxTsIndex.CHANGE_TO_EVEN_SCRAMBLED;
+    /**
+     * TS index CHANGE_TO_ODD_SCRAMBLED.
+     * @hide
+     */
+    public static final int TS_INDEX_CHANGE_TO_ODD_SCRAMBLED =
+            Constants.DemuxTsIndex.CHANGE_TO_ODD_SCRAMBLED;
+    /**
+     * TS index DISCONTINUITY_INDICATOR.
+     * @hide
+     */
+    public static final int TS_INDEX_DISCONTINUITY_INDICATOR =
+            Constants.DemuxTsIndex.DISCONTINUITY_INDICATOR;
+    /**
+     * TS index RANDOM_ACCESS_INDICATOR.
+     * @hide
+     */
+    public static final int TS_INDEX_RANDOM_ACCESS_INDICATOR =
+            Constants.DemuxTsIndex.RANDOM_ACCESS_INDICATOR;
+    /**
+     * TS index PRIORITY_INDICATOR.
+     * @hide
+     */
+    public static final int TS_INDEX_PRIORITY_INDICATOR = Constants.DemuxTsIndex.PRIORITY_INDICATOR;
+    /**
+     * TS index PCR_FLAG.
+     * @hide
+     */
+    public static final int TS_INDEX_PCR_FLAG = Constants.DemuxTsIndex.PCR_FLAG;
+    /**
+     * TS index OPCR_FLAG.
+     * @hide
+     */
+    public static final int TS_INDEX_OPCR_FLAG = Constants.DemuxTsIndex.OPCR_FLAG;
+    /**
+     * TS index SPLICING_POINT_FLAG.
+     * @hide
+     */
+    public static final int TS_INDEX_SPLICING_POINT_FLAG =
+            Constants.DemuxTsIndex.SPLICING_POINT_FLAG;
+    /**
+     * TS index PRIVATE_DATA.
+     * @hide
+     */
+    public static final int TS_INDEX_PRIVATE_DATA = Constants.DemuxTsIndex.PRIVATE_DATA;
+    /**
+     * TS index ADAPTATION_EXTENSION_FLAG.
+     * @hide
+     */
+    public static final int TS_INDEX_ADAPTATION_EXTENSION_FLAG =
+            Constants.DemuxTsIndex.ADAPTATION_EXTENSION_FLAG;
+
+    /**
+     * Indexes can be tagged by Start Code in PES (Packetized Elementary Stream)
+     * according to ISO/IEC 13818-1.
+     * @hide
+     */
+    @IntDef(flag = true, value = {SC_INDEX_I_FRAME, SC_INDEX_P_FRAME, SC_INDEX_B_FRAME,
+            SC_INDEX_SEQUENCE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScIndex {}
+
+    /**
+     * SC index for a new I-frame.
+     * @hide
+     */
+    public static final int SC_INDEX_I_FRAME = Constants.DemuxScIndex.I_FRAME;
+    /**
+     * SC index for a new P-frame.
+     * @hide
+     */
+    public static final int SC_INDEX_P_FRAME = Constants.DemuxScIndex.P_FRAME;
+    /**
+     * SC index for a new B-frame.
+     * @hide
+     */
+    public static final int SC_INDEX_B_FRAME = Constants.DemuxScIndex.B_FRAME;
+    /**
+     * SC index for a new sequence.
+     * @hide
+     */
+    public static final int SC_INDEX_SEQUENCE = Constants.DemuxScIndex.SEQUENCE;
+
+
+    /**
+     * Indexes can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2.
+     *
+     * @hide
+     */
+    @IntDef(flag = true,
+            value = {SC_HEVC_INDEX_SPS, SC_HEVC_INDEX_AUD, SC_HEVC_INDEX_SLICE_CE_BLA_W_LP,
+            SC_HEVC_INDEX_SLICE_BLA_W_RADL, SC_HEVC_INDEX_SLICE_BLA_N_LP,
+            SC_HEVC_INDEX_SLICE_IDR_W_RADL, SC_HEVC_INDEX_SLICE_IDR_N_LP,
+            SC_HEVC_INDEX_SLICE_TRAIL_CRA})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScHevcIndex {}
+
+    /**
+     * SC HEVC index SPS.
+     * @hide
+     */
+    public static final int SC_HEVC_INDEX_SPS = Constants.DemuxScHevcIndex.SPS;
+    /**
+     * SC HEVC index AUD.
+     * @hide
+     */
+    public static final int SC_HEVC_INDEX_AUD = Constants.DemuxScHevcIndex.AUD;
+    /**
+     * SC HEVC index SLICE_CE_BLA_W_LP.
+     * @hide
+     */
+    public static final int SC_HEVC_INDEX_SLICE_CE_BLA_W_LP =
+            Constants.DemuxScHevcIndex.SLICE_CE_BLA_W_LP;
+    /**
+     * SC HEVC index SLICE_BLA_W_RADL.
+     * @hide
+     */
+    public static final int SC_HEVC_INDEX_SLICE_BLA_W_RADL =
+            Constants.DemuxScHevcIndex.SLICE_BLA_W_RADL;
+    /**
+     * SC HEVC index SLICE_BLA_N_LP.
+     * @hide
+     */
+    public static final int SC_HEVC_INDEX_SLICE_BLA_N_LP =
+            Constants.DemuxScHevcIndex.SLICE_BLA_N_LP;
+    /**
+     * SC HEVC index SLICE_IDR_W_RADL.
+     * @hide
+     */
+    public static final int SC_HEVC_INDEX_SLICE_IDR_W_RADL =
+            Constants.DemuxScHevcIndex.SLICE_IDR_W_RADL;
+    /**
+     * SC HEVC index SLICE_IDR_N_LP.
+     * @hide
+     */
+    public static final int SC_HEVC_INDEX_SLICE_IDR_N_LP =
+            Constants.DemuxScHevcIndex.SLICE_IDR_N_LP;
+    /**
+     * SC HEVC index SLICE_TRAIL_CRA.
+     * @hide
+     */
+    public static final int SC_HEVC_INDEX_SLICE_TRAIL_CRA =
+            Constants.DemuxScHevcIndex.SLICE_TRAIL_CRA;
+
+
     /** @hide */
     @IntDef({FRONTEND_SCAN_UNDEFINED, FRONTEND_SCAN_AUTO, FRONTEND_SCAN_BLIND})
     @Retention(RetentionPolicy.SOURCE)
@@ -727,73 +831,6 @@
             Constants.FrontendDvbtHierarchy.HIERARCHY_4_INDEPTH;
 
     /** @hide */
-    @IntDef({FRONTEND_ANALOG_TYPE_UNDEFINED, FRONTEND_ANALOG_TYPE_PAL, FRONTEND_ANALOG_TYPE_SECAM,
-            FRONTEND_ANALOG_TYPE_NTSC})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendAnalogType {}
-    /** @hide */
-    public static final int FRONTEND_ANALOG_TYPE_UNDEFINED = Constants.FrontendAnalogType.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_TYPE_PAL = Constants.FrontendAnalogType.PAL;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_TYPE_SECAM = Constants.FrontendAnalogType.SECAM;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_TYPE_NTSC = Constants.FrontendAnalogType.NTSC;
-
-    /** @hide */
-    @IntDef({FRONTEND_ANALOG_SIF_UNDEFINED, FRONTEND_ANALOG_SIF_BG, FRONTEND_ANALOG_SIF_BG_A2,
-            FRONTEND_ANALOG_SIF_BG_NICAM, FRONTEND_ANALOG_SIF_I, FRONTEND_ANALOG_SIF_DK,
-            FRONTEND_ANALOG_SIF_DK1, FRONTEND_ANALOG_SIF_DK2, FRONTEND_ANALOG_SIF_DK3,
-            FRONTEND_ANALOG_SIF_DK_NICAM, FRONTEND_ANALOG_SIF_L, FRONTEND_ANALOG_SIF_M,
-            FRONTEND_ANALOG_SIF_M_BTSC, FRONTEND_ANALOG_SIF_M_A2, FRONTEND_ANALOG_SIF_M_EIA_J,
-            FRONTEND_ANALOG_SIF_I_NICAM, FRONTEND_ANALOG_SIF_L_NICAM, FRONTEND_ANALOG_SIF_L_PRIME})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FrontendAnalogSifStandard {}
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_UNDEFINED =
-            Constants.FrontendAnalogSifStandard.UNDEFINED;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_BG = Constants.FrontendAnalogSifStandard.BG;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_BG_A2 = Constants.FrontendAnalogSifStandard.BG_A2;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_BG_NICAM =
-            Constants.FrontendAnalogSifStandard.BG_NICAM;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_I = Constants.FrontendAnalogSifStandard.I;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_DK = Constants.FrontendAnalogSifStandard.DK;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_DK1 = Constants.FrontendAnalogSifStandard.DK1;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_DK2 = Constants.FrontendAnalogSifStandard.DK2;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_DK3 = Constants.FrontendAnalogSifStandard.DK3;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_DK_NICAM =
-            Constants.FrontendAnalogSifStandard.DK_NICAM;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_L = Constants.FrontendAnalogSifStandard.L;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_M = Constants.FrontendAnalogSifStandard.M;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_M_BTSC = Constants.FrontendAnalogSifStandard.M_BTSC;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_M_A2 = Constants.FrontendAnalogSifStandard.M_A2;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_M_EIA_J =
-            Constants.FrontendAnalogSifStandard.M_EIA_J;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_I_NICAM =
-            Constants.FrontendAnalogSifStandard.I_NICAM;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_L_NICAM =
-            Constants.FrontendAnalogSifStandard.L_NICAM;
-    /** @hide */
-    public static final int FRONTEND_ANALOG_SIF_L_PRIME =
-            Constants.FrontendAnalogSifStandard.L_PRIME;
-
-    /** @hide */
     @IntDef({FRONTEND_ATSC_MODULATION_UNDEFINED, FRONTEND_ATSC_MODULATION_AUTO,
             FRONTEND_ATSC_MODULATION_MOD_8VSB, FRONTEND_ATSC_MODULATION_MOD_16VSB})
     @Retention(RetentionPolicy.SOURCE)
@@ -1252,79 +1289,40 @@
     /** @hide */
     public static final int FILTER_SETTINGS_ALP = Constants.DemuxFilterMainType.ALP;
 
-    /** @hide */
-    @IntDef({DVR_SETTINGS_RECORD, DVR_SETTINGS_PLAYBACK})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface DvrSettingsType {}
-    /** @hide */
-    public static final int DVR_SETTINGS_RECORD = Constants.DvrType.RECORD;
-    /** @hide */
-    public static final int DVR_SETTINGS_PLAYBACK = Constants.DvrType.PLAYBACK;
-
-
-    /** @hide */
-    @IntDef({LNB_VOLTAGE_NONE, LNB_VOLTAGE_5V, LNB_VOLTAGE_11V, LNB_VOLTAGE_12V, LNB_VOLTAGE_13V,
-            LNB_VOLTAGE_14V, LNB_VOLTAGE_15V, LNB_VOLTAGE_18V, LNB_VOLTAGE_19V})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface LnbVoltage {}
-    /** @hide */
-    public static final int LNB_VOLTAGE_NONE = Constants.LnbVoltage.NONE;
-    /** @hide */
-    public static final int LNB_VOLTAGE_5V = Constants.LnbVoltage.VOLTAGE_5V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_11V = Constants.LnbVoltage.VOLTAGE_11V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_12V = Constants.LnbVoltage.VOLTAGE_12V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_13V = Constants.LnbVoltage.VOLTAGE_13V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_14V = Constants.LnbVoltage.VOLTAGE_14V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_15V = Constants.LnbVoltage.VOLTAGE_15V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_18V = Constants.LnbVoltage.VOLTAGE_18V;
-    /** @hide */
-    public static final int LNB_VOLTAGE_19V = Constants.LnbVoltage.VOLTAGE_19V;
-
-    /** @hide */
-    @IntDef({LNB_TONE_NONE, LNB_TONE_CONTINUOUS})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface LnbTone {}
-    /** @hide */
-    public static final int LNB_TONE_NONE = Constants.LnbTone.NONE;
-    /** @hide */
-    public static final int LNB_TONE_CONTINUOUS = Constants.LnbTone.CONTINUOUS;
-
-    /** @hide */
-    @IntDef({LNB_POSITION_UNDEFINED, LNB_POSITION_A, LNB_POSITION_B})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface LnbPosition {}
-    /** @hide */
-    public static final int LNB_POSITION_UNDEFINED = Constants.LnbPosition.UNDEFINED;
-    /** @hide */
-    public static final int LNB_POSITION_A = Constants.LnbPosition.POSITION_A;
-    /** @hide */
-    public static final int LNB_POSITION_B = Constants.LnbPosition.POSITION_B;
-
 
     /** @hide */
     @IntDef({RESULT_SUCCESS, RESULT_UNAVAILABLE, RESULT_NOT_INITIALIZED, RESULT_INVALID_STATE,
             RESULT_INVALID_ARGUMENT, RESULT_OUT_OF_MEMORY, RESULT_UNKNOWN_ERROR})
     @Retention(RetentionPolicy.SOURCE)
     public @interface Result {}
-    /** @hide */
+
+    /**
+     * Operation succeeded.
+     */
     public static final int RESULT_SUCCESS = Constants.Result.SUCCESS;
-    /** @hide */
+    /**
+     * Operation failed because the corresponding resources are not available.
+     */
     public static final int RESULT_UNAVAILABLE = Constants.Result.UNAVAILABLE;
-    /** @hide */
+    /**
+     * Operation failed because the corresponding resources are not initialized.
+     */
     public static final int RESULT_NOT_INITIALIZED = Constants.Result.NOT_INITIALIZED;
-    /** @hide */
+    /**
+     * Operation failed because it's not in a valid state.
+     */
     public static final int RESULT_INVALID_STATE = Constants.Result.INVALID_STATE;
-    /** @hide */
+    /**
+     * Operation failed because there are invalid arguments.
+     */
     public static final int RESULT_INVALID_ARGUMENT = Constants.Result.INVALID_ARGUMENT;
-    /** @hide */
+    /**
+     * Memory allocation failed.
+     */
     public static final int RESULT_OUT_OF_MEMORY = Constants.Result.OUT_OF_MEMORY;
-    /** @hide */
+    /**
+     * Operation failed due to unknown errors.
+     */
     public static final int RESULT_UNKNOWN_ERROR = Constants.Result.UNKNOWN_ERROR;
 
     private TunerConstants() {
diff --git a/media/java/android/media/tv/tuner/dvr/Dvr.java b/media/java/android/media/tv/tuner/dvr/Dvr.java
new file mode 100644
index 0000000..f90042b
--- /dev/null
+++ b/media/java/android/media/tv/tuner/dvr/Dvr.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.dvr;
+
+import android.annotation.BytesLong;
+import android.annotation.NonNull;
+import android.media.tv.tuner.Tuner.DvrCallback;
+import android.media.tv.tuner.Tuner.Filter;
+import android.media.tv.tuner.TunerConstants.Result;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Digital Video Record (DVR) interface provides record control on Demux's output buffer and
+ * playback control on Demux's input buffer.
+ *
+ * @hide
+ */
+public class Dvr {
+    private long mNativeContext;
+    private DvrCallback mCallback;
+
+    private native int nativeAttachFilter(Filter filter);
+    private native int nativeDetachFilter(Filter filter);
+    private native int nativeConfigureDvr(DvrSettings settings);
+    private native int nativeStartDvr();
+    private native int nativeStopDvr();
+    private native int nativeFlushDvr();
+    private native int nativeClose();
+    private native void nativeSetFileDescriptor(int fd);
+    private native int nativeRead(long size);
+    private native int nativeRead(byte[] bytes, long offset, long size);
+    private native int nativeWrite(long size);
+    private native int nativeWrite(byte[] bytes, long offset, long size);
+
+    private Dvr() {}
+
+    /**
+     * Attaches a filter to DVR interface for recording.
+     *
+     * @param filter the filter to be attached.
+     * @return result status of the operation.
+     */
+    @Result
+    public int attachFilter(@NonNull Filter filter) {
+        return nativeAttachFilter(filter);
+    }
+
+    /**
+     * Detaches a filter from DVR interface.
+     *
+     * @param filter the filter to be detached.
+     * @return result status of the operation.
+     */
+    @Result
+    public int detachFilter(@NonNull Filter filter) {
+        return nativeDetachFilter(filter);
+    }
+
+    /**
+     * Configures the DVR.
+     *
+     * @param settings the settings of the DVR interface.
+     * @return result status of the operation.
+     */
+    @Result
+    public int configure(@NonNull DvrSettings settings) {
+        return nativeConfigureDvr(settings);
+    }
+
+    /**
+     * Starts DVR.
+     *
+     * <p>Starts consuming playback data or producing data for recording.
+     *
+     * @return result status of the operation.
+     */
+    @Result
+    public int start() {
+        return nativeStartDvr();
+    }
+
+    /**
+     * Stops DVR.
+     *
+     * <p>Stops consuming playback data or producing data for recording.
+     *
+     * @return result status of the operation.
+     */
+    @Result
+    public int stop() {
+        return nativeStopDvr();
+    }
+
+    /**
+     * Flushed DVR data.
+     *
+     * <p>The data in DVR buffer is cleared.
+     *
+     * @return result status of the operation.
+     */
+    @Result
+    public int flush() {
+        return nativeFlushDvr();
+    }
+
+    /**
+     * Closes the DVR instance to release resources.
+     *
+     * @return result status of the operation.
+     */
+    @Result
+    public int close() {
+        return nativeClose();
+    }
+
+    /**
+     * Sets file descriptor to read/write data.
+     *
+     * @param fd the file descriptor to read/write data.
+     */
+    public void setFileDescriptor(@NonNull ParcelFileDescriptor fd) {
+        nativeSetFileDescriptor(fd.getFd());
+    }
+
+    /**
+     * Reads data from the file for DVR playback.
+     *
+     * @param size the maximum number of bytes to read.
+     * @return the number of bytes read.
+     */
+    public int read(@BytesLong long size) {
+        return nativeRead(size);
+    }
+
+    /**
+     * Reads data from the buffer for DVR playback and copies to the given byte array.
+     *
+     * @param bytes the byte array to store the data.
+     * @param offset the index of the first byte in {@code bytes} to copy to.
+     * @param size the maximum number of bytes to read.
+     * @return the number of bytes read.
+     */
+    public int read(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) {
+        if (size + offset > bytes.length) {
+            throw new ArrayIndexOutOfBoundsException(
+                    "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size);
+        }
+        return nativeRead(bytes, offset, size);
+    }
+
+    /**
+     * Writes recording data to file.
+     *
+     * @param size the maximum number of bytes to write.
+     * @return the number of bytes written.
+     */
+    public int write(@BytesLong long size) {
+        return nativeWrite(size);
+    }
+
+    /**
+     * Writes recording data to buffer.
+     *
+     * @param bytes the byte array stores the data to be written to DVR.
+     * @param offset the index of the first byte in {@code bytes} to be written to DVR.
+     * @param size the maximum number of bytes to write.
+     * @return the number of bytes written.
+     */
+    public int write(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) {
+        return nativeWrite(bytes, offset, size);
+    }
+}
diff --git a/media/java/android/media/tv/tuner/dvr/DvrSettings.java b/media/java/android/media/tv/tuner/dvr/DvrSettings.java
new file mode 100644
index 0000000..46efd7a
--- /dev/null
+++ b/media/java/android/media/tv/tuner/dvr/DvrSettings.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.dvr;
+
+import android.annotation.BytesLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerConstants.FilterStatus;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * DVR settings used to configure {@link Dvr}.
+ *
+ * @hide
+ */
+public class DvrSettings {
+
+    /** @hide */
+    @IntDef(prefix = "DATA_FORMAT_",
+            value = {DATA_FORMAT_TS, DATA_FORMAT_PES, DATA_FORMAT_ES, DATA_FORMAT_SHV_TLV})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DataFormat {}
+
+    /**
+     * Transport Stream.
+     */
+    public static final int DATA_FORMAT_TS = Constants.DataFormat.TS;
+    /**
+     * Packetized Elementary Stream.
+     */
+    public static final int DATA_FORMAT_PES = Constants.DataFormat.PES;
+    /**
+     * Elementary Stream.
+     */
+    public static final int DATA_FORMAT_ES = Constants.DataFormat.ES;
+    /**
+     * TLV (type-length-value) Stream for SHV
+     */
+    public static final int DATA_FORMAT_SHV_TLV = Constants.DataFormat.SHV_TLV;
+
+
+    /** @hide */
+    @IntDef(prefix = "TYPE_", value = {TYPE_RECORD, TYPE_PLAYBACK})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
+    /**
+     * DVR for recording.
+     */
+    public static final int TYPE_RECORD = Constants.DvrType.RECORD;
+    /**
+     * DVR for playback of recorded programs.
+     */
+    public static final int TYPE_PLAYBACK = Constants.DvrType.PLAYBACK;
+
+
+
+    private final int mStatusMask;
+    private final long mLowThreshold;
+    private final long mHighThreshold;
+    private final long mPacketSize;
+
+    @DataFormat
+    private final int mDataFormat;
+    @Type
+    private final int mType;
+
+    private DvrSettings(int statusMask, long lowThreshold, long highThreshold, long packetSize,
+            @DataFormat int dataFormat, @Type int type) {
+        mStatusMask = statusMask;
+        mLowThreshold = lowThreshold;
+        mHighThreshold = highThreshold;
+        mPacketSize = packetSize;
+        mDataFormat = dataFormat;
+        mType = type;
+    }
+
+    /**
+     * Creates a builder for {@link DvrSettings}.
+     */
+    @NonNull
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    /**
+     * Builder for {@link DvrSettings}.
+     */
+    public static final class Builder {
+        private int mStatusMask;
+        private long mLowThreshold;
+        private long mHighThreshold;
+        private long mPacketSize;
+        @DataFormat
+        private int mDataFormat;
+        @Type
+        private int mType;
+
+        /**
+         * Sets status mask.
+         */
+        @NonNull
+        public Builder setStatusMask(@FilterStatus int statusMask) {
+            this.mStatusMask = statusMask;
+            return this;
+        }
+
+        /**
+         * Sets low threshold in bytes.
+         */
+        @NonNull
+        public Builder setLowThreshold(@BytesLong long lowThreshold) {
+            this.mLowThreshold = lowThreshold;
+            return this;
+        }
+
+        /**
+         * Sets high threshold in bytes.
+         */
+        @NonNull
+        public Builder setHighThreshold(@BytesLong long highThreshold) {
+            this.mHighThreshold = highThreshold;
+            return this;
+        }
+
+        /**
+         * Sets packet size in bytes.
+         */
+        @NonNull
+        public Builder setPacketSize(@BytesLong long packetSize) {
+            this.mPacketSize = packetSize;
+            return this;
+        }
+
+        /**
+         * Sets data format.
+         */
+        @NonNull
+        public Builder setDataFormat(@DataFormat int dataFormat) {
+            this.mDataFormat = dataFormat;
+            return this;
+        }
+
+        /**
+         * Sets settings type.
+         */
+        @NonNull
+        public Builder setType(@Type int type) {
+            this.mType = type;
+            return this;
+        }
+
+        /**
+         * Builds a {@link DvrSettings} object.
+         */
+        @NonNull
+        public DvrSettings build() {
+            return new DvrSettings(
+                    mStatusMask, mLowThreshold, mHighThreshold, mPacketSize, mDataFormat, mType);
+        }
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/AudioDescriptor.java b/media/java/android/media/tv/tuner/filter/AudioDescriptor.java
new file mode 100644
index 0000000..c88c07f
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/AudioDescriptor.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.filter;
+
+/**
+ * Meta data from AD (Audio Descriptor) according to ETSI TS 101 154 V2.1.1.
+ *
+ * @hide
+ */
+public class AudioDescriptor {
+    private final byte mAdFade;
+    private final byte mAdPan;
+    private final char mVersionTextTag;
+    private final byte mAdGainCenter;
+    private final byte mAdGainFront;
+    private final byte mAdGainSurround;
+
+    // This constructor is used by JNI code only
+    private AudioDescriptor(byte adFade, byte adPan, char versionTextTag, byte adGainCenter,
+            byte adGainFront, byte adGainSurround) {
+        mAdFade = adFade;
+        mAdPan = adPan;
+        mVersionTextTag = versionTextTag;
+        mAdGainCenter = adGainCenter;
+        mAdGainFront = adGainFront;
+        mAdGainSurround = adGainSurround;
+    }
+
+    /**
+     * Gets AD fade byte.
+     *
+     * <p>Takes values between 0x00 (representing no fade of the main programme sound) and 0xFF
+     * (representing a full fade). Over the range 0x00 to 0xFE one lsb represents a step in
+     * attenuation of the programme sound of 0.3 dB giving a range of 76.2 dB. The fade value of
+     * 0xFF represents no programme sound at all (i.e. mute).
+     */
+    public byte getAdFade() {
+        return mAdFade;
+    }
+
+    /**
+     * Gets AD pan byte.
+     *
+     * <p>Takes values between 0x00 representing a central forward presentation of the audio
+     * description and 0xFF, each increment representing a 360/256 degree step clockwise looking
+     * down on the listener (i.e. just over 1.4 degrees).
+     */
+    public byte getAdPan() {
+        return mAdPan;
+    }
+
+    /**
+     * Gets AD version tag. A single ASCII character version indicates the version.
+     *
+     * <p>A single ASCII character version designator (here "1" indicates revision 1).
+     */
+    public char getVersionTextTag() {
+        return mVersionTextTag;
+    }
+
+    /**
+     * Gets AD gain byte center in dB.
+     *
+     * <p>Represents a signed value in dB. Takes values between 0x7F (representing +76.2 dB boost of
+     * the main programme center) and 0x80 (representing a full fade). Over the range 0x00 to 0x7F
+     * one lsb represents a step in boost of the programme center of 0.6 dB giving a maximum boost
+     * of +76.2 dB. Over the range 0x81 to 0x00 one lsb represents a step in attenuation of the
+     * programme center of 0.6 dB giving a maximum attenuation of -76.2 dB. The gain value of 0x80
+     * represents no main center level at all (i.e. mute).
+     */
+    public byte getAdGainCenter() {
+        return mAdGainCenter;
+    }
+
+    /**
+     * Gets AD gain byte front in dB.
+     *
+     * <p>Same as {@link #getAdGainCenter()}, but applied to left and right front channel.
+     */
+    public byte getAdGainFront() {
+        return mAdGainFront;
+    }
+
+    /**
+     * Gets AD gain byte surround in dB.
+     *
+     * <p>Same as {@link #getAdGainCenter()}, but applied to all surround channels
+     */
+    public byte getAdGainSurround() {
+        return mAdGainSurround;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java b/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
deleted file mode 100644
index 306de84..0000000
--- a/media/java/android/media/tv/tuner/filter/AudioExtraMetaData.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.tv.tuner.filter;
-
-/**
- * Extra Meta Data from AD (Audio Descriptor) according to
- * ETSI TS 101 154 V2.1.1.
- * @hide
- */
-public class AudioExtraMetaData {
-    private byte mAdFade;
-    private byte mAdPan;
-    private byte mVersionTextTag;
-    private byte mAdGainCenter;
-    private byte mAdGainFront;
-    private byte mAdGainSurround;
-}
diff --git a/media/java/android/media/tv/tuner/filter/DownloadEvent.java b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
index 548fa77..591e4e5 100644
--- a/media/java/android/media/tv/tuner/filter/DownloadEvent.java
+++ b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
@@ -16,15 +16,65 @@
 
 package android.media.tv.tuner.filter;
 
+import android.media.tv.tuner.Tuner.Filter;
+
 /**
- * Download event.
+ * Filter event sent from {@link Filter} objects with download type.
+ *
  * @hide
  */
 public class DownloadEvent extends FilterEvent {
-    private int mItemId;
-    private int mMpuSequenceNumber;
-    private int mItemFragmentIndex;
-    private int mLastItemFragmentIndex;
-    private int mDataLength;
+    private final int mItemId;
+    private final int mMpuSequenceNumber;
+    private final int mItemFragmentIndex;
+    private final int mLastItemFragmentIndex;
+    private final int mDataLength;
+
+    // This constructor is used by JNI code only
+    private DownloadEvent(int itemId, int mpuSequenceNumber, int itemFragmentIndex,
+            int lastItemFragmentIndex, int dataLength) {
+        mItemId = itemId;
+        mMpuSequenceNumber = mpuSequenceNumber;
+        mItemFragmentIndex = itemFragmentIndex;
+        mLastItemFragmentIndex = lastItemFragmentIndex;
+        mDataLength = dataLength;
+    }
+
+    /**
+     * Gets item ID.
+     */
+    public int getItemId() {
+        return mItemId;
+    }
+
+    /**
+     * Gets MPU sequence number of filtered data.
+     */
+    public int getMpuSequenceNumber() {
+        return mMpuSequenceNumber;
+    }
+
+    /**
+     * Gets current index of the current item.
+     *
+     * An item can be stored in different fragments.
+     */
+    public int getItemFragmentIndex() {
+        return mItemFragmentIndex;
+    }
+
+    /**
+     * Gets last index of the current item.
+     */
+    public int getLastItemFragmentIndex() {
+        return mLastItemFragmentIndex;
+    }
+
+    /**
+     * Gets data size in bytes of filtered data.
+     */
+    public int getDataLength() {
+        return mDataLength;
+    }
 }
 
diff --git a/media/java/android/media/tv/tuner/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
similarity index 70%
rename from media/java/android/media/tv/tuner/Filter.java
rename to media/java/android/media/tv/tuner/filter/Filter.java
index db3b97a..804c0c5 100644
--- a/media/java/android/media/tv/tuner/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -14,33 +14,33 @@
  * limitations under the License.
  */
 
-package android.media.tv.tuner;
+package android.media.tv.tuner.filter;
 
+import android.annotation.BytesLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.media.tv.tuner.Tuner.FilterCallback;
-import android.media.tv.tuner.filter.FilterConfiguration;
-import android.media.tv.tuner.filter.Settings;
 
 /**
  * Tuner data filter.
  *
- * <p> This class is used to filter wanted data according to the filter's configuration.
+ * <p>This class is used to filter wanted data according to the filter's configuration.
+ *
  * @hide
  */
 public class Filter implements AutoCloseable {
     private long mNativeContext;
     private FilterCallback mCallback;
-    int mId;
+    private final int mId;
 
     private native int nativeConfigureFilter(
             int type, int subType, FilterConfiguration settings);
     private native int nativeGetId();
-    private native int nativeSetDataSource(Tuner.Filter source);
+    private native int nativeSetDataSource(Filter source);
     private native int nativeStartFilter();
     private native int nativeStopFilter();
     private native int nativeFlushFilter();
-    private native int nativeRead(byte[] buffer, int offset, int size);
+    private native int nativeRead(byte[] buffer, long offset, long size);
     private native int nativeClose();
 
     private Filter(int id) {
@@ -53,24 +53,20 @@
     /**
      * Configures the filter.
      *
-     * @param settings the settings of the filter.
+     * @param config the configuration of the filter.
      * @return result status of the operation.
-     * @hide
      */
-    public int configure(FilterConfiguration settings) {
+    public int configure(@NonNull FilterConfiguration config) {
         int subType = -1;
-        Settings s = settings.getSettings();
+        Settings s = config.getSettings();
         if (s != null) {
             subType = s.getType();
         }
-        return nativeConfigureFilter(settings.getType(), subType, settings);
+        return nativeConfigureFilter(config.getType(), subType, config);
     }
 
     /**
      * Gets the filter Id.
-     *
-     * @return the hardware resource Id for the filter.
-     * @hide
      */
     public int getId() {
         return nativeGetId();
@@ -87,17 +83,15 @@
      * @param source the filter instance which provides data input. Switch to
      * use demux as data source if the filter instance is NULL.
      * @return result status of the operation.
-     * @hide
      */
-    public int setDataSource(@Nullable Tuner.Filter source) {
+    public int setDataSource(@Nullable Filter source) {
         return nativeSetDataSource(source);
     }
 
     /**
-     * Starts the filter.
+     * Starts filtering data.
      *
      * @return result status of the operation.
-     * @hide
      */
     public int start() {
         return nativeStartFilter();
@@ -105,35 +99,38 @@
 
 
     /**
-     * Stops the filter.
+     * Stops filtering data.
      *
      * @return result status of the operation.
-     * @hide
      */
     public int stop() {
         return nativeStopFilter();
     }
 
     /**
-     * Flushes the filter.
+     * Flushes the filter. Data in filter buffer is cleared.
      *
      * @return result status of the operation.
-     * @hide
      */
     public int flush() {
         return nativeFlushFilter();
     }
 
-    /** @hide */
-    public int read(@NonNull byte[] buffer, int offset, int size) {
+    /**
+     * Copies filtered data from filter buffer to the given byte array.
+     *
+     * @param buffer the buffer to store the filtered data.
+     * @param offset the index of the first byte in {@code buffer} to write.
+     * @param size the maximum number of bytes to read.
+     * @return the number of bytes read.
+     */
+    public int read(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) {
         size = Math.min(size, buffer.length - offset);
         return nativeRead(buffer, offset, size);
     }
 
     /**
-     * Release the Filter instance.
-     *
-     * @hide
+     * Releases the Filter instance.
      */
     @Override
     public void close() {
diff --git a/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java b/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java
index 4da1d21..09489ed 100644
--- a/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java
+++ b/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java
@@ -16,10 +16,25 @@
 
 package android.media.tv.tuner.filter;
 
+import android.media.tv.tuner.Tuner.Filter;
+
 /**
- * IP payload event.
+ * Filter event sent from {@link Filter} objects with IP payload type.
+ *
  * @hide
  */
 public class IpPayloadEvent extends FilterEvent {
-    private int mDataLength;
+    private final int mDataLength;
+
+    // This constructor is used by JNI code only
+    private IpPayloadEvent(int dataLength) {
+        mDataLength = dataLength;
+    }
+
+    /**
+     * Gets data size in bytes of filtered data.
+     */
+    public int getDataLength() {
+        return mDataLength;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java
index 7703248..37f94ae 100644
--- a/media/java/android/media/tv/tuner/filter/MediaEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java
@@ -16,20 +16,111 @@
 
 package android.media.tv.tuner.filter;
 
-import android.os.NativeHandle;
+import android.annotation.Nullable;
+import android.media.tv.tuner.Tuner.Filter;
 
 /**
- * Media event.
+ * Filter event sent from {@link Filter} objects with media type.
+ *
  * @hide
  */
-public class MediaEvent extends FilterEvent {
-    private int mStreamId;
-    private boolean mIsPtsPresent;
-    private long mPts;
-    private int mDataLength;
-    private NativeHandle mHandle;
-    private boolean mIsSecureMemory;
-    private int mMpuSequenceNumber;
-    private boolean mIsPrivateData;
-    private AudioExtraMetaData mExtraMetaData;
+public class MediaEvent extends FilterEvent{
+    private final int mStreamId;
+    private final boolean mIsPtsPresent;
+    private final long mPts;
+    private final int mDataLength;
+    private final Object mLinearBuffer;
+    private final boolean mIsSecureMemory;
+    private final int mMpuSequenceNumber;
+    private final boolean mIsPrivateData;
+    private final AudioDescriptor mExtraMetaData;
+
+    // This constructor is used by JNI code only
+    private MediaEvent(int streamId, boolean isPtsPresent, long pts, int dataLength, Object buffer,
+            boolean isSecureMemory, int mpuSequenceNumber, boolean isPrivateData,
+            AudioDescriptor extraMetaData) {
+        mStreamId = streamId;
+        mIsPtsPresent = isPtsPresent;
+        mPts = pts;
+        mDataLength = dataLength;
+        mLinearBuffer = buffer;
+        mIsSecureMemory = isSecureMemory;
+        mMpuSequenceNumber = mpuSequenceNumber;
+        mIsPrivateData = isPrivateData;
+        mExtraMetaData = extraMetaData;
+    }
+
+    /**
+     * Gets stream ID.
+     */
+    public int getStreamId() {
+        return mStreamId;
+    }
+
+    /**
+     * Returns whether PTS is present.
+     *
+     * @return {@code true} if PTS is present in PES header; {@code false} otherwise.
+     */
+    public boolean getIsPtsPresent() {
+        return mIsPtsPresent;
+    }
+
+    /**
+     * Gets PTS (Presentation Time Stamp) for audio or video frame.
+     */
+    public long getPts() {
+        return mPts;
+    }
+
+    /**
+     * Gets data size in bytes of audio or video frame.
+     */
+    public int getDataLength() {
+        return mDataLength;
+    }
+
+    /**
+     * Gets a linear buffer associated to the memory where audio or video data stays.
+     * TODO: use LinearBuffer when it's ready.
+     *
+     * @hide
+     */
+    public Object getLinearBuffer() {
+        return mLinearBuffer;
+    }
+
+    /**
+     * Returns whether the data is secure.
+     *
+     * @return {@code true} if the data is in secure area, and isn't mappable;
+     *         {@code false} otherwise.
+     */
+    public boolean getIsSecureMemory() {
+        return mIsSecureMemory;
+    }
+
+    /**
+     * Gets MPU sequence number of filtered data.
+     */
+    public int getMpuSequenceNumber() {
+        return mMpuSequenceNumber;
+    }
+
+    /**
+     * Returns whether the data is private.
+     *
+     * @return {@code true} if the data is in private; {@code false} otherwise.
+     */
+    public boolean getIsPrivateData() {
+        return mIsPrivateData;
+    }
+
+    /**
+     * Gets audio extra metadata.
+     */
+    @Nullable
+    public AudioDescriptor getExtraMetaData() {
+        return mExtraMetaData;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
index dbd8c77..7f37994 100644
--- a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
@@ -16,11 +16,34 @@
 
 package android.media.tv.tuner.filter;
 
+import android.media.tv.tuner.Tuner.Filter;
+
 /**
- * MMPT record event.
+ * Filter event sent from {@link Filter} objects with MMTP type.
+ *
  * @hide
  */
 public class MmtpRecordEvent extends FilterEvent {
-    private int mScHevcIndexMask;
-    private long mByteNumber;
+    private final int mScHevcIndexMask;
+    private final long mByteNumber;
+
+    // This constructor is used by JNI code only
+    private MmtpRecordEvent(int scHevcIndexMask, long byteNumber) {
+        mScHevcIndexMask = scHevcIndexMask;
+        mByteNumber = byteNumber;
+    }
+
+    /**
+     * Gets indexes which can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2.
+     */
+    public int getScHevcIndexMask() {
+        return mScHevcIndexMask;
+    }
+
+    /**
+     * Gets the byte number from beginning of the filter's output.
+     */
+    public long getByteNumber() {
+        return mByteNumber;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/PesEvent.java b/media/java/android/media/tv/tuner/filter/PesEvent.java
index 16536e2..60251bf 100644
--- a/media/java/android/media/tv/tuner/filter/PesEvent.java
+++ b/media/java/android/media/tv/tuner/filter/PesEvent.java
@@ -16,12 +16,43 @@
 
 package android.media.tv.tuner.filter;
 
+import android.media.tv.tuner.Tuner.Filter;
+
 /**
- * PES event.
+ * Filter event sent from {@link Filter} objects with PES type.
+ *
  * @hide
  */
 public class PesEvent extends FilterEvent {
-    private int mStreamId;
-    private int mDataLength;
-    private int mMpuSequenceNumber;
+    private final int mStreamId;
+    private final int mDataLength;
+    private final int mMpuSequenceNumber;
+
+    // This constructor is used by JNI code only
+    private PesEvent(int streamId, int dataLength, int mpuSequenceNumber) {
+        mStreamId = streamId;
+        mDataLength = dataLength;
+        mMpuSequenceNumber = mpuSequenceNumber;
+    }
+
+    /**
+     * Gets stream ID.
+     */
+    public int getStreamId() {
+        return mStreamId;
+    }
+
+    /**
+     * Gets data size in bytes of filtered data.
+     */
+    public int getDataLength() {
+        return mDataLength;
+    }
+
+    /**
+     * Gets MPU sequence number of filtered data.
+     */
+    public int getMpuSequenceNumber() {
+        return mMpuSequenceNumber;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/TemiEvent.java b/media/java/android/media/tv/tuner/filter/TemiEvent.java
index 3841604..031fa5c 100644
--- a/media/java/android/media/tv/tuner/filter/TemiEvent.java
+++ b/media/java/android/media/tv/tuner/filter/TemiEvent.java
@@ -16,12 +16,46 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.NonNull;
+import android.media.tv.tuner.Tuner.Filter;
+
 /**
- * TEMI event.
+ * Filter event sent from {@link Filter} objects for Timed External Media Information (TEMI) data.
+ *
  * @hide
  */
 public class TemiEvent extends FilterEvent {
-    private long mPts;
-    private byte mDescrTag;
-    private byte[] mDescrData;
+    private final long mPts;
+    private final byte mDescrTag;
+    private final byte[] mDescrData;
+
+    // This constructor is used by JNI code only
+    private TemiEvent(long pts, byte descrTag, byte[] descrData) {
+        mPts = pts;
+        mDescrTag = descrTag;
+        mDescrData = descrData;
+    }
+
+
+    /**
+     * Gets PTS (Presentation Time Stamp) for audio or video frame.
+     */
+    public long getPts() {
+        return mPts;
+    }
+
+    /**
+     * Gets TEMI descriptor tag.
+     */
+    public byte getDescriptorTag() {
+        return mDescrTag;
+    }
+
+    /**
+     * Gets TEMI descriptor.
+     */
+    @NonNull
+    public byte[] getDescriptorData() {
+        return mDescrData;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/TimeFilter.java b/media/java/android/media/tv/tuner/filter/TimeFilter.java
similarity index 98%
rename from media/java/android/media/tv/tuner/TimeFilter.java
rename to media/java/android/media/tv/tuner/filter/TimeFilter.java
index 8bd0d26..c975004 100644
--- a/media/java/android/media/tv/tuner/TimeFilter.java
+++ b/media/java/android/media/tv/tuner/filter/TimeFilter.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.media.tv.tuner;
+package android.media.tv.tuner.filter;
 
 import android.annotation.Nullable;
 import android.media.tv.tuner.TunerConstants.Result;
diff --git a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
index 875b5bd..fa4dd72 100644
--- a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
@@ -16,12 +16,84 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.IntDef;
+import android.media.tv.tuner.Tuner.Filter;
+import android.media.tv.tuner.TunerConstants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
- * TS record event.
+ * Filter event sent from {@link Filter} objects for TS record data.
+ *
  * @hide
  */
 public class TsRecordEvent extends FilterEvent {
-    private int mTpid;
-    private int mIndexMask;
-    private long mByteNumber;
+    /**
+     * @hide
+     */
+    @IntDef(flag = true, value = {
+            TunerConstants.TS_INDEX_FIRST_PACKET,
+            TunerConstants.TS_INDEX_PAYLOAD_UNIT_START_INDICATOR,
+            TunerConstants.TS_INDEX_CHANGE_TO_NOT_SCRAMBLED,
+            TunerConstants.TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED,
+            TunerConstants.TS_INDEX_CHANGE_TO_ODD_SCRAMBLED,
+            TunerConstants.TS_INDEX_DISCONTINUITY_INDICATOR,
+            TunerConstants.TS_INDEX_RANDOM_ACCESS_INDICATOR,
+            TunerConstants.TS_INDEX_PRIORITY_INDICATOR,
+            TunerConstants.TS_INDEX_PCR_FLAG,
+            TunerConstants.TS_INDEX_OPCR_FLAG,
+            TunerConstants.TS_INDEX_SPLICING_POINT_FLAG,
+            TunerConstants.TS_INDEX_PRIVATE_DATA,
+            TunerConstants.TS_INDEX_ADAPTATION_EXTENSION_FLAG,
+            TunerConstants.SC_INDEX_I_FRAME,
+            TunerConstants.SC_INDEX_P_FRAME,
+            TunerConstants.SC_INDEX_B_FRAME,
+            TunerConstants.SC_INDEX_SEQUENCE,
+            TunerConstants.SC_HEVC_INDEX_SPS,
+            TunerConstants.SC_HEVC_INDEX_AUD,
+            TunerConstants.SC_HEVC_INDEX_SLICE_CE_BLA_W_LP,
+            TunerConstants.SC_HEVC_INDEX_SLICE_BLA_W_RADL,
+            TunerConstants.SC_HEVC_INDEX_SLICE_BLA_N_LP,
+            TunerConstants.SC_HEVC_INDEX_SLICE_IDR_W_RADL,
+            TunerConstants.SC_HEVC_INDEX_SLICE_IDR_N_LP,
+            TunerConstants.SC_HEVC_INDEX_SLICE_TRAIL_CRA,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface IndexMask {}
+
+    private final int mPid;
+    private final int mIndexMask;
+    private final long mByteNumber;
+
+    // This constructor is used by JNI code only
+    private TsRecordEvent(int pid, int indexMask, long byteNumber) {
+        mPid = pid;
+        mIndexMask = indexMask;
+        mByteNumber = byteNumber;
+    }
+
+    /**
+     * Gets packet ID.
+     */
+    public int getTpid() {
+        return mPid;
+    }
+
+    /**
+     * Gets index mask.
+     *
+     * <p>The index type is one of TS, SC, and SC-HEVC, and is set when configuring the filter.
+     */
+    @IndexMask
+    public int getIndexMask() {
+        return mIndexMask;
+    }
+
+    /**
+     * Gets the byte number from beginning of the filter's output.
+     */
+    public long getByteNumber() {
+        return mByteNumber;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
index 16308ce..aec8ce8 100644
--- a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
@@ -16,33 +16,153 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.tv.tuner.V1_0.Constants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
- * Frontend settings for analog.
+ * Frontend settings for analog tuner.
+ *
  * @hide
  */
 public class AnalogFrontendSettings extends FrontendSettings {
-    private int mAnalogType;
-    private int mSifStandard;
+    /** @hide */
+    @IntDef(flag = true, value = {SIGNAL_TYPE_UNDEFINED, SIGNAL_TYPE_PAL, SIGNAL_TYPE_SECAM,
+            SIGNAL_TYPE_NTSC})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SignalType {}
+
+    /**
+     * Undefined analog signal type.
+     */
+    public static final int SIGNAL_TYPE_UNDEFINED = Constants.FrontendAnalogType.UNDEFINED;
+    /**
+     * PAL analog signal type.
+     */
+    public static final int SIGNAL_TYPE_PAL = Constants.FrontendAnalogType.PAL;
+    /**
+     * SECM analog signal type.
+     */
+    public static final int SIGNAL_TYPE_SECAM = Constants.FrontendAnalogType.SECAM;
+    /**
+     * NTSC analog signal type.
+     */
+    public static final int SIGNAL_TYPE_NTSC = Constants.FrontendAnalogType.NTSC;
+
+
+    /** @hide */
+    @IntDef(flag = true, value = {SIF_UNDEFINED, SIF_BG, SIF_BG_A2, SIF_BG_NICAM, SIF_I, SIF_DK,
+            SIF_DK1, SIF_DK2, SIF_DK3, SIF_DK_NICAM, SIF_L, SIF_M, SIF_M_BTSC, SIF_M_A2,
+            SIF_M_EIA_J, SIF_I_NICAM, SIF_L_NICAM, SIF_L_PRIME})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SifStandard {}
+
+    /**
+     * Undefined Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_UNDEFINED = Constants.FrontendAnalogSifStandard.UNDEFINED;
+    /**
+     * BG Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_BG = Constants.FrontendAnalogSifStandard.BG;
+    /**
+     * BG-A2 Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_BG_A2 = Constants.FrontendAnalogSifStandard.BG_A2;
+    /**
+     * BG-NICAM Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_BG_NICAM = Constants.FrontendAnalogSifStandard.BG_NICAM;
+    /**
+     * I Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_I = Constants.FrontendAnalogSifStandard.I;
+    /**
+     * DK Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_DK = Constants.FrontendAnalogSifStandard.DK;
+    /**
+     * DK1 Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_DK1 = Constants.FrontendAnalogSifStandard.DK1;
+    /**
+     * DK2 Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_DK2 = Constants.FrontendAnalogSifStandard.DK2;
+    /**
+     * DK3 Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_DK3 = Constants.FrontendAnalogSifStandard.DK3;
+    /**
+     * DK-NICAM Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_DK_NICAM = Constants.FrontendAnalogSifStandard.DK_NICAM;
+    /**
+     * L Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_L = Constants.FrontendAnalogSifStandard.L;
+    /**
+     * M Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_M = Constants.FrontendAnalogSifStandard.M;
+    /**
+     * M-BTSC Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_M_BTSC = Constants.FrontendAnalogSifStandard.M_BTSC;
+    /**
+     * M-A2 Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_M_A2 = Constants.FrontendAnalogSifStandard.M_A2;
+    /**
+     * M-EIA-J Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_M_EIA_J = Constants.FrontendAnalogSifStandard.M_EIA_J;
+    /**
+     * I-NICAM Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_I_NICAM = Constants.FrontendAnalogSifStandard.I_NICAM;
+    /**
+     * L-NICAM Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_L_NICAM = Constants.FrontendAnalogSifStandard.L_NICAM;
+    /**
+     * L-PRIME Analog Standard Interchange Format (SIF).
+     */
+    public static final int SIF_L_PRIME = Constants.FrontendAnalogSifStandard.L_PRIME;
+
+
+    private final int mAnalogType;
+    private final int mSifStandard;
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ANALOG;
+        return FrontendSettings.TYPE_ANALOG;
     }
 
+
+    /**
+     * Gets analog signal type.
+     */
+    @SignalType
     public int getAnalogType() {
         return mAnalogType;
     }
 
+    /**
+     * Gets Standard Interchange Format (SIF).
+     */
+    @SifStandard
     public int getSifStandard() {
         return mSifStandard;
     }
 
     /**
-     * Creates a new builder object.
+     * Creates a builder for {@link AnalogFrontendSettings}.
      */
+    @NonNull
     public static Builder newBuilder() {
         return new Builder();
     }
@@ -54,7 +174,7 @@
     }
 
     /**
-     * Builder for FrontendAnalogSettings.
+     * Builder for {@link AnalogFrontendSettings}.
      */
     public static class Builder {
         private int mFrequency;
@@ -64,8 +184,9 @@
         private Builder() {}
 
         /**
-         * Sets frequency.
+         * Sets frequency in Hz.
          */
+        @NonNull
         public Builder setFrequency(int frequency) {
             mFrequency = frequency;
             return this;
@@ -74,22 +195,25 @@
         /**
          * Sets analog type.
          */
-        public Builder setAnalogType(int analogType) {
+        @NonNull
+        public Builder setAnalogType(@SignalType int analogType) {
             mAnalogType = analogType;
             return this;
         }
 
         /**
-         * Sets sif standard.
+         * Sets Standard Interchange Format (SIF).
          */
-        public Builder setSifStandard(int sifStandard) {
+        @NonNull
+        public Builder setSifStandard(@SifStandard int sifStandard) {
             mSifStandard = sifStandard;
             return this;
         }
 
         /**
-         * Builds a FrontendAnalogSettings instance.
+         * Builds a {@link AnalogFrontendSettings} object.
          */
+        @NonNull
         public AnalogFrontendSettings build() {
             return new AnalogFrontendSettings(mFrequency, mAnalogType, mSifStandard);
         }
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
index bce8a64..5b09e36 100644
--- a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
@@ -16,10 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
 import java.util.List;
 
 /**
@@ -37,6 +33,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ATSC3;
+        return FrontendSettings.TYPE_ATSC3;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
index 14c5cdd..19e18d0 100644
--- a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
@@ -16,9 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Frontend settings for ATSC.
  * @hide
@@ -32,6 +29,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ATSC;
+        return FrontendSettings.TYPE_ATSC;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
index 07e49ff..60618f6 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
@@ -16,9 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Frontend settings for DVBC.
  * @hide
@@ -37,6 +34,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_DVBC;
+        return FrontendSettings.TYPE_DVBC;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
index 23c0a7b1..586787f 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
@@ -16,9 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Frontend settings for DVBS.
  * @hide
@@ -38,6 +35,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_DVBS;
+        return FrontendSettings.TYPE_DVBS;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
index eec00f3..6b350a7 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
@@ -16,10 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Frontend settings for DVBT.
  * @hide
@@ -45,6 +41,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_DVBT;
+        return FrontendSettings.TYPE_DVBT;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
index 5d03570..99e8dd2 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
@@ -16,7 +16,7 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.TunerConstants.FrontendType;
+import android.media.tv.tuner.frontend.FrontendSettings.Type;
 
 /**
  * Frontend info.
@@ -54,7 +54,7 @@
         return mId;
     }
     /** Gets frontend type. */
-    @FrontendType
+    @Type
     public int getType() {
         return mType;
     }
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
new file mode 100644
index 0000000..210aef4
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.hardware.tv.tuner.V1_0.Constants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Frontend settings for tune and scan operations.
+ *
+ * @hide
+ */
+public abstract class FrontendSettings {
+    /** @hide */
+    @IntDef({TYPE_UNDEFINED, TYPE_ANALOG, TYPE_ATSC, TYPE_ATSC3, TYPE_DVBC, TYPE_DVBS, TYPE_DVBT,
+            TYPE_ISDBS, TYPE_ISDBS3, TYPE_ISDBT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
+    /**
+     * Undefined frontend type.
+     */
+    public static final int TYPE_UNDEFINED = Constants.FrontendType.UNDEFINED;
+    /**
+     * Analog frontend type.
+     */
+    public static final int TYPE_ANALOG = Constants.FrontendType.ANALOG;
+    /**
+     * Advanced Television Systems Committee (ATSC) frontend type.
+     */
+    public static final int TYPE_ATSC = Constants.FrontendType.ATSC;
+    /**
+     * Advanced Television Systems Committee 3.0 (ATSC-3) frontend type.
+     */
+    public static final int TYPE_ATSC3 = Constants.FrontendType.ATSC3;
+    /**
+     * Digital Video Broadcasting-Cable (DVB-C) frontend type.
+     */
+    public static final int TYPE_DVBC = Constants.FrontendType.DVBC;
+    /**
+     * Digital Video Broadcasting-Satellite (DVB-S) frontend type.
+     */
+    public static final int TYPE_DVBS = Constants.FrontendType.DVBS;
+    /**
+     * Digital Video Broadcasting-Terrestrial (DVB-T) frontend type.
+     */
+    public static final int TYPE_DVBT = Constants.FrontendType.DVBT;
+    /**
+     * Integrated Services Digital Broadcasting-Satellite (ISDB-S) frontend type.
+     */
+    public static final int TYPE_ISDBS = Constants.FrontendType.ISDBS;
+    /**
+     * Integrated Services Digital Broadcasting-Satellite 3 (ISDB-S3) frontend type.
+     */
+    public static final int TYPE_ISDBS3 = Constants.FrontendType.ISDBS3;
+    /**
+     * Integrated Services Digital Broadcasting-Terrestrial (ISDB-T) frontend type.
+     */
+    public static final int TYPE_ISDBT = Constants.FrontendType.ISDBT;
+
+    private final int mFrequency;
+
+    /** @hide */
+    public FrontendSettings(int frequency) {
+        mFrequency = frequency;
+    }
+
+    /**
+     * Returns the frontend type.
+     */
+    @Type
+    public abstract int getType();
+
+    /**
+     * Gets the frequency.
+     *
+     * @return the frequency in Hz.
+     */
+    public int getFrequency() {
+        return mFrequency;
+    }
+
+}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
index 89ec536..fb5d62a 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -16,13 +16,13 @@
 
 package android.media.tv.tuner.frontend;
 
+import android.media.tv.tuner.Lnb;
 import android.media.tv.tuner.TunerConstants;
 import android.media.tv.tuner.TunerConstants.FrontendDvbcSpectralInversion;
 import android.media.tv.tuner.TunerConstants.FrontendDvbtHierarchy;
 import android.media.tv.tuner.TunerConstants.FrontendInnerFec;
 import android.media.tv.tuner.TunerConstants.FrontendModulation;
 import android.media.tv.tuner.TunerConstants.FrontendStatusType;
-import android.media.tv.tuner.TunerConstants.LnbVoltage;
 
 /**
  * Frontend status
@@ -128,7 +128,7 @@
         return (int) mValue;
     }
     /** Power Voltage Type for LNB. */
-    @LnbVoltage
+    @Lnb.Voltage
     public int getLnbVoltage() {
         if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LNB_VOLTAGE) {
             throw new IllegalStateException();
diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
index 736d0b1..45932a7 100644
--- a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
@@ -16,9 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Frontend settings for ISDBS-3.
  * @hide
@@ -37,6 +34,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ISDBS3;
+        return FrontendSettings.TYPE_ISDBS3;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
index 7fd5da7..e726a9a 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
@@ -16,9 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Frontend settings for ISDBS.
  * @hide
@@ -37,6 +34,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ISDBS;
+        return FrontendSettings.TYPE_ISDBS;
     }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
index 3f83267..f2b7d24 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
@@ -16,10 +16,6 @@
 
 package android.media.tv.tuner.frontend;
 
-
-import android.media.tv.tuner.FrontendSettings;
-import android.media.tv.tuner.TunerConstants;
-
 /**
  * Frontend settings for ISDBT.
  * @hide
@@ -37,6 +33,6 @@
 
     @Override
     public int getType() {
-        return TunerConstants.FRONTEND_TYPE_ISDBT;
+        return FrontendSettings.TYPE_ISDBT;
     }
 }
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index ee67613..536a061 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -143,6 +143,10 @@
         "libutils",
     ],
 
+    header_libs: [
+        "libstagefright_foundation_headers",
+    ],
+
     export_include_dirs: ["."],
 
     cflags: [
diff --git a/media/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 24ea3cf..6554267 100644
--- a/mms/java/android/telephony/MmsManager.java
+++ b/mms/java/android/telephony/MmsManager.java
@@ -16,11 +16,8 @@
 
 package android.telephony;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.ActivityThread;
 import android.app.PendingIntent;
-import android.content.Context;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.RemoteException;
@@ -30,16 +27,22 @@
 
 /**
  * Manages MMS operations such as sending multimedia messages.
+ * Get this object by calling the static method {@link #getInstance()}.
+ * @hide
  */
-public final class MmsManager {
+public class MmsManager {
     private static final String TAG = "MmsManager";
-    private final Context mContext;
+
+    /** Singleton object constructed during class initialization. */
+    private static final MmsManager sInstance = new MmsManager();
 
     /**
-     * @hide
+     * Get the MmsManager singleton instance.
+     *
+     * @return the {@link MmsManager} singleton instance.
      */
-    public MmsManager(@NonNull Context context) {
-        mContext = context;
+    public static MmsManager getInstance() {
+        return sInstance;
     }
 
     /**
@@ -53,9 +56,8 @@
      * @param sentIntent if not NULL this <code>PendingIntent</code> is broadcast when the message
      *                   is successfully sent, or failed
      */
-    public void sendMultimediaMessage(int subId, @NonNull Uri contentUri,
-            @Nullable String locationUrl, @Nullable Bundle configOverrides,
-            @Nullable PendingIntent sentIntent) {
+    public void sendMultimediaMessage(int subId, Uri contentUri, String locationUrl,
+            Bundle configOverrides, PendingIntent sentIntent) {
         try {
             final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
             if (iMms == null) {
@@ -82,9 +84,8 @@
      *  broadcast when the message is downloaded, or the download is failed
      * @throws IllegalArgumentException if locationUrl or contentUri is empty
      */
-    public void downloadMultimediaMessage(int subId, @NonNull String locationUrl,
-            @NonNull Uri contentUri, @Nullable Bundle configOverrides,
-            @Nullable PendingIntent downloadedIntent) {
+    public void downloadMultimediaMessage(int subId, String locationUrl, Uri contentUri,
+            Bundle configOverrides, 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/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
index f7802d2..6ec3854 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
@@ -19,14 +19,19 @@
 import android.service.notification.StatusBarNotification;
 
 import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.util.leak.LeakDetector;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
+import dagger.Lazy;
+
 /**
  * Car specific notification entry manager that does nothing when adding a notification.
  *
@@ -42,8 +47,12 @@
             NotificationGroupManager groupManager,
             NotificationRankingManager rankingManager,
             KeyguardEnvironment keyguardEnvironment,
-            FeatureFlags featureFlags) {
-        super(notifLog, groupManager, rankingManager, keyguardEnvironment, featureFlags);
+            FeatureFlags featureFlags,
+            Lazy<NotificationRowBinder> notificationRowBinderLazy,
+            Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
+            LeakDetector leakDetector) {
+        super(notifLog, groupManager, rankingManager, keyguardEnvironment, featureFlags,
+                notificationRowBinderLazy, notificationRemoteInputManagerLazy, leakDetector);
     }
 
     @Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 3c3ebe2..4521f5f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.car;
 
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
 import android.animation.Animator;
@@ -107,6 +106,7 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -268,7 +268,6 @@
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
             BypassHeadsUpNotifier bypassHeadsUpNotifier,
-            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
             Lazy<NewNotifPipeline> newNotifPipeline,
             FalsingManager falsingManager,
             BroadcastDispatcher broadcastDispatcher,
@@ -334,6 +333,7 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
+            NotificationRowBinderImpl notificationRowBinder,
             DismissCallbackRegistry dismissCallbackRegistry,
             /* Car Settings injected components. */
             CarServiceProvider carServiceProvider,
@@ -355,7 +355,6 @@
                 headsUpManagerPhone,
                 dynamicPrivacyController,
                 bypassHeadsUpNotifier,
-                allowNotificationLongPress,
                 newNotifPipeline,
                 falsingManager,
                 broadcastDispatcher,
@@ -421,6 +420,7 @@
                 keyguardDismissUtil,
                 extensionController,
                 userInfoControllerImpl,
+                notificationRowBinder,
                 dismissCallbackRegistry);
         mScrimController = scrimController;
         mLockscreenLockIconController = lockscreenLockIconController;
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
index a1eccce..e5a091f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.car;
 
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
 import android.content.Context;
@@ -67,6 +66,7 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -138,7 +138,6 @@
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
             BypassHeadsUpNotifier bypassHeadsUpNotifier,
-            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
             Lazy<NewNotifPipeline> newNotifPipeline,
             FalsingManager falsingManager,
             BroadcastDispatcher broadcastDispatcher,
@@ -204,6 +203,7 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
+            NotificationRowBinderImpl notificationRowBinder,
             DismissCallbackRegistry dismissCallbackRegistry,
             CarServiceProvider carServiceProvider,
             Lazy<PowerManagerHelper> powerManagerHelperLazy,
@@ -224,7 +224,6 @@
                 headsUpManagerPhone,
                 dynamicPrivacyController,
                 bypassHeadsUpNotifier,
-                allowNotificationLongPress,
                 newNotifPipeline,
                 falsingManager,
                 broadcastDispatcher,
@@ -289,6 +288,7 @@
                 keyguardDismissUtil,
                 extensionController,
                 userInfoControllerImpl,
+                notificationRowBinder,
                 dismissCallbackRegistry,
                 carServiceProvider,
                 powerManagerHelperLazy,
diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
index 69bd0ed..ff00fb3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
@@ -16,8 +16,6 @@
 
 package com.android.settingslib;
 
-import static android.content.Context.TELEPHONY_SERVICE;
-
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -172,36 +170,38 @@
         }
     }
 
-    public static String getFormattedPhoneNumber(Context context, SubscriptionInfo subscriptionInfo) {
+    /**
+     * Format a phone number.
+     * @param subscriptionInfo {@link SubscriptionInfo} subscription information.
+     * @return Returns formatted phone number.
+     */
+    public static String getFormattedPhoneNumber(Context context,
+            SubscriptionInfo subscriptionInfo) {
         String formattedNumber = null;
         if (subscriptionInfo != null) {
-            final TelephonyManager telephonyManager =
-                    (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
-            final String rawNumber =
-                    telephonyManager.getLine1Number(subscriptionInfo.getSubscriptionId());
+            final TelephonyManager telephonyManager = context.getSystemService(
+                    TelephonyManager.class);
+            final String rawNumber = telephonyManager.createForSubscriptionId(
+                    subscriptionInfo.getSubscriptionId()).getLine1Number();
             if (!TextUtils.isEmpty(rawNumber)) {
                 formattedNumber = PhoneNumberUtils.formatNumber(rawNumber);
             }
-
         }
         return formattedNumber;
     }
 
     public static String getFormattedPhoneNumbers(Context context,
-            List<SubscriptionInfo> subscriptionInfo) {
+            List<SubscriptionInfo> subscriptionInfoList) {
         StringBuilder sb = new StringBuilder();
-        if (subscriptionInfo != null) {
-            final TelephonyManager telephonyManager =
-                    (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
-            final int count = subscriptionInfo.size();
-            for (int i = 0; i < count; i++) {
-                final String rawNumber = telephonyManager.getLine1Number(
-                        subscriptionInfo.get(i).getSubscriptionId());
+        if (subscriptionInfoList != null) {
+            final TelephonyManager telephonyManager = context.getSystemService(
+                    TelephonyManager.class);
+            final int count = subscriptionInfoList.size();
+            for (SubscriptionInfo subscriptionInfo : subscriptionInfoList) {
+                final String rawNumber = telephonyManager.createForSubscriptionId(
+                        subscriptionInfo.getSubscriptionId()).getLine1Number();
                 if (!TextUtils.isEmpty(rawNumber)) {
-                    sb.append(PhoneNumberUtils.formatNumber(rawNumber));
-                    if (i < count - 1) {
-                        sb.append("\n");
-                    }
+                    sb.append(PhoneNumberUtils.formatNumber(rawNumber)).append("\n");
                 }
             }
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index a2bd210..a784e04 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
@@ -148,17 +151,18 @@
     }
 
     public boolean connect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.connect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        // Downgrade priority as user is disconnecting the headset.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        if (mService == null) {
+            return false;
         }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -182,12 +186,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -197,11 +201,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
     boolean isA2dpPlaying() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
index bc03c34..8ca5a74 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothA2dpSink;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
@@ -116,18 +119,15 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        // Downgrade priority as user is disconnecting the headset.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -141,12 +141,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -156,11 +156,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index 560cb3b..d65b5da 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -112,18 +115,15 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        // Downgrade priority as user is disconnecting the headset.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -165,12 +165,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -180,11 +180,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index b4b55f3..9f1af66 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -146,17 +149,18 @@
     }
 
     public boolean connect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.connect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        // Downgrade priority as user is disconnecting the hearing aid.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        if (mService == null) {
+            return false;
         }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -180,12 +184,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -195,11 +199,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
index a372e23..678f2e3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -126,7 +129,7 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     @Override
@@ -134,11 +137,8 @@
         if (mService == null) {
             return false;
         }
-        // Downgrade priority as user is disconnecting the headset.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Override
@@ -154,13 +154,13 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     @Override
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -171,11 +171,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index 975a1e6..588083e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -99,13 +102,17 @@
     }
 
     public boolean connect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.connect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.disconnect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -119,12 +126,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) != CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -132,11 +139,11 @@
     public void setPreferred(BluetoothDevice device, boolean preferred) {
         if (mService == null) return;
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
index 95139a1..7d121aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -115,18 +118,15 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        // Downgrade priority as user is disconnecting.
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -140,12 +140,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -155,11 +155,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
index 31a0eea..a96a4e7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -119,10 +121,8 @@
         if (mService == null) {
             return false;
         }
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -136,12 +136,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -155,7 +155,7 @@
                 mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
index 4ea0df6..56267fc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -129,7 +132,7 @@
             return false;
         }
         Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress());
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
@@ -137,7 +140,7 @@
         if (mService == null) {
             return false;
         }
-        return mService.disconnect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -151,12 +154,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -166,11 +169,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
index 3f920a8..f7c0bf5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -96,8 +98,10 @@
     }
 
     public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.disconnect(device);
+        if (mService == null) {
+            return false;
+        }
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
index 0ca4d61..3022c5b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -112,17 +115,15 @@
         if (mService == null) {
             return false;
         }
-        return mService.connect(device);
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
     }
 
     public boolean disconnect(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
-        }
-        return mService.disconnect(device);
+
+        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
     }
 
     public int getConnectionStatus(BluetoothDevice device) {
@@ -136,12 +137,12 @@
         if (mService == null) {
             return false;
         }
-        return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
     public int getPreferred(BluetoothDevice device) {
         if (mService == null) {
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
@@ -151,11 +152,11 @@
             return;
         }
         if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
index ebca962..853c77e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -38,7 +38,7 @@
         final SubscriptionManager subscriptionManager = context.getSystemService(
                 SubscriptionManager.class);
         final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
-                telephonyManager.getSubscriberId(subId));
+                telephonyManager.createForSubscriptionId(subId).getSubscriberId());
 
         if (!subscriptionManager.isActiveSubId(subId)) {
             Log.i(TAG, "Subscription is not active: " + subId);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
index 976445e..ccb6646 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
     @Test
     public void connect_shouldConnectBluetoothA2dpSink() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothA2dpSink() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
index 69c020d..9180760 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
     @Test
     public void connect_shouldConnectBluetoothHeadsetClient() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothHeadsetClient() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
index 6f66709..1425c38 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
     @Test
     public void connect_shouldConnectBluetoothMapClient() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothMapClient() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
index b21ec9c3..15f560b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
     @Test
     public void connect_shouldConnectBluetoothPbapClient() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothPbapClient() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
index ec88034..4f978a8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.verify;
@@ -66,13 +69,13 @@
     @Test
     public void connect_shouldConnectBluetoothSap() {
         mProfile.connect(mBluetoothDevice);
-        verify(mService).connect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
     }
 
     @Test
     public void disconnect_shouldDisconnectBluetoothSap() {
         mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
+        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
     }
 
     @Test
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 347d6c2..1c63efc 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -194,6 +194,9 @@
 
     <uses-permission android:name="android.permission.MANAGE_APPOPS" />
 
+    <!-- Permission required for storage tests - FuseDaemonHostTest -->
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+
     <!-- Permission needed to run network tests in CTS -->
     <uses-permission android:name="android.permission.MANAGE_TEST_NETWORKS" />
     <!-- Permission needed to test tcp keepalive offload. -->
diff --git a/packages/SystemUI/docs/broadcasts.md b/packages/SystemUI/docs/broadcasts.md
index 56a637f..28657f2 100644
--- a/packages/SystemUI/docs/broadcasts.md
+++ b/packages/SystemUI/docs/broadcasts.md
@@ -42,24 +42,29 @@
 
 ```kotlin
 /**
-    * Register a receiver for broadcast with the dispatcher
-    *
-    * @param receiver A receiver to dispatch the [Intent]
-    * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
-    *               It will only take into account actions and categories for filtering. It must
-    *               have at least one action.
-    * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. By default, it is the
-    *                main handler. Pass `null` to use the default.
-    * @param user A user handle to determine which broadcast should be dispatched to this receiver.
-    *             By default, it is the current user.
-    * @throws IllegalArgumentException if the filter has other constraints that are not actions or
-    *                                  categories or the filter has no actions.
-    */
+ * Register a receiver for broadcast with the dispatcher
+ *
+ * @param receiver A receiver to dispatch the [Intent]
+ * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
+ *               It will only take into account actions and categories for filtering. It must
+ *               have at least one action.
+ * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an
+ *                 executor in the main thread (default).
+ * @param user A user handle to determine which broadcast should be dispatched to this receiver.
+ *             By default, it is the current user.
+ * @throws IllegalArgumentException if the filter has other constraints that are not actions or
+ *                                  categories or the filter has no actions.
+ */
 @JvmOverloads
-fun registerReceiver(BroadcastReceiver, IntentFilter, Handler? = mainHandler, UserHandle = context.user)
+fun registerReceiver(
+        BroadcastReceiver, 
+        IntentFilter, 
+        Executor? = context.mainExecutor,
+        UserHandle = context.user
+) {
 ```
 
-All subscriptions are done with the same overloaded method. As specified in the doc, in order to pass a `UserHandle` with the default `Handler`, pass `null` for the `Handler`.
+All subscriptions are done with the same overloaded method. As specified in the doc, in order to pass a `UserHandle` with the default `Executor`, pass `null` for the `Executor`.
 
 In the same way as with `Context`, subscribing the same `BroadcastReceiver` for the same user using different filters will result on two subscriptions, not in replacing the filter.
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 431862f..a58e3d7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1638,7 +1638,7 @@
         filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
-        broadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mHandler);
+        broadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, mHandler);
 
         final IntentFilter allUserFilter = new IntentFilter();
         allUserFilter.addAction(Intent.ACTION_USER_INFO_CHANGED);
@@ -1649,8 +1649,8 @@
         allUserFilter.addAction(ACTION_USER_UNLOCKED);
         allUserFilter.addAction(ACTION_USER_STOPPED);
         allUserFilter.addAction(ACTION_USER_REMOVED);
-        broadcastDispatcher.registerReceiver(mBroadcastAllReceiver, allUserFilter, mHandler,
-                UserHandle.ALL);
+        broadcastDispatcher.registerReceiverWithHandler(mBroadcastAllReceiver, allUserFilter,
+                mHandler, UserHandle.ALL);
 
         mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
         try {
diff --git a/packages/SystemUI/src/com/android/systemui/DumpController.kt b/packages/SystemUI/src/com/android/systemui/DumpController.kt
index 8c7075b..f14c4cd 100644
--- a/packages/SystemUI/src/com/android/systemui/DumpController.kt
+++ b/packages/SystemUI/src/com/android/systemui/DumpController.kt
@@ -30,6 +30,14 @@
 /**
  * Controller that allows any [Dumpable] to subscribe and be dumped along with other SystemUI
  * dependencies.
+ *
+ * To dump a specific dumpable on-demand:
+ *
+ * ```
+ * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService dependency DumpController <tag1>,<tag2>,<tag3>
+ * ```
+ *
+ * Where tag1, tag2, etc. are the tags of the dumpables you want to dump.
  */
 @Singleton
 class DumpController @Inject constructor() : Dumpable {
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 0e736dc..c533755 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -266,7 +266,7 @@
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_SWITCHED);
-        mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter, mHandler);
+        mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);
 
         mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 8cb0cc5..cedf7c3 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.content.IntentFilter
 import android.os.Handler
+import android.os.HandlerExecutor
 import android.os.Looper
 import android.os.Message
 import android.os.UserHandle
@@ -32,13 +33,14 @@
 import com.android.systemui.dagger.qualifiers.Main
 import java.io.FileDescriptor
 import java.io.PrintWriter
+import java.util.concurrent.Executor
 import javax.inject.Inject
 import javax.inject.Singleton
 
 data class ReceiverData(
     val receiver: BroadcastReceiver,
     val filter: IntentFilter,
-    val handler: Handler,
+    val executor: Executor,
     val user: UserHandle
 )
 
@@ -76,8 +78,33 @@
      * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
      *               It will only take into account actions and categories for filtering. It must
      *               have at least one action.
-     * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. By default, it is the
-     *                main handler. Pass `null` to use the default.
+     * @param handler A handler to dispatch [BroadcastReceiver.onReceive].
+     * @param user A user handle to determine which broadcast should be dispatched to this receiver.
+     *             By default, it is the current user.
+     * @throws IllegalArgumentException if the filter has other constraints that are not actions or
+     *                                  categories or the filter has no actions.
+     */
+    @Deprecated(message = "Replacing Handler for Executor in SystemUI",
+            replaceWith = ReplaceWith("registerReceiver(receiver, filter, executor, user)"))
+    @JvmOverloads
+    fun registerReceiverWithHandler(
+        receiver: BroadcastReceiver,
+        filter: IntentFilter,
+        handler: Handler,
+        user: UserHandle = context.user
+    ) {
+        registerReceiver(receiver, filter, HandlerExecutor(handler), user)
+    }
+
+    /**
+     * Register a receiver for broadcast with the dispatcher
+     *
+     * @param receiver A receiver to dispatch the [Intent]
+     * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
+     *               It will only take into account actions and categories for filtering. It must
+     *               have at least one action.
+     * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an
+     *                 executor in the main thread (default).
      * @param user A user handle to determine which broadcast should be dispatched to this receiver.
      *             By default, it is the current user.
      * @throws IllegalArgumentException if the filter has other constraints that are not actions or
@@ -85,15 +112,15 @@
      */
     @JvmOverloads
     fun registerReceiver(
-        receiver: BroadcastReceiver,
-        filter: IntentFilter,
-        handler: Handler? = mainHandler,
-        user: UserHandle = context.user
+            receiver: BroadcastReceiver,
+            filter: IntentFilter,
+            executor: Executor? = context.mainExecutor,
+            user: UserHandle = context.user
     ) {
         checkFilter(filter)
         this.handler
                 .obtainMessage(MSG_ADD_RECEIVER,
-                ReceiverData(receiver, filter, handler ?: mainHandler, user))
+                        ReceiverData(receiver, filter, executor ?: context.mainExecutor, user))
                 .sendToTarget()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index b2942bb..0c631aa 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -193,7 +193,7 @@
                         it.filter.hasAction(intent.action) &&
                             it.filter.matchCategories(intent.categories) == null }
                     ?.forEach {
-                        it.handler.post {
+                        it.executor.execute {
                             if (DEBUG) Log.w(TAG,
                                     "[$index] Dispatching ${intent.action} to ${it.receiver}")
                             it.receiver.pendingResult = pendingResult
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 97224f1..ccbbb24 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -111,7 +111,10 @@
     }
 
     private final Context mContext;
+    /** Bubbles that are actively in the stack. */
     private final List<Bubble> mBubbles;
+    /** Bubbles that are being loaded but haven't been added to the stack just yet. */
+    private final List<Bubble> mPendingBubbles;
     private Bubble mSelectedBubble;
     private boolean mExpanded;
     private final int mMaxBubbles;
@@ -143,6 +146,7 @@
     public BubbleData(Context context) {
         mContext = context;
         mBubbles = new ArrayList<>();
+        mPendingBubbles = new ArrayList<>();
         mStateChange = new Update(mBubbles);
         mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered);
     }
@@ -188,7 +192,15 @@
     Bubble getOrCreateBubble(NotificationEntry entry) {
         Bubble bubble = getBubbleWithKey(entry.getKey());
         if (bubble == null) {
+            // Check for it in pending
+            for (int i = 0; i < mPendingBubbles.size(); i++) {
+                Bubble b = mPendingBubbles.get(i);
+                if (b.getKey().equals(entry.getKey())) {
+                    return b;
+                }
+            }
             bubble = new Bubble(entry);
+            mPendingBubbles.add(bubble);
         } else {
             bubble.setEntry(entry);
         }
@@ -204,7 +216,7 @@
         if (DEBUG_BUBBLE_DATA) {
             Log.d(TAG, "notificationEntryUpdated: " + bubble);
         }
-
+        mPendingBubbles.remove(bubble); // No longer pending once we're here
         Bubble prevBubble = getBubbleWithKey(bubble.getKey());
         suppressFlyout |= !shouldShowFlyout(bubble.getEntry());
 
@@ -377,6 +389,12 @@
     }
 
     private void doRemove(String key, @DismissReason int reason) {
+        //  If it was pending remove it
+        for (int i = 0; i < mPendingBubbles.size(); i++) {
+            if (mPendingBubbles.get(i).getKey().equals(key)) {
+                mPendingBubbles.remove(mPendingBubbles.get(i));
+            }
+        }
         int indexToRemove = indexForKey(key);
         if (indexToRemove == -1) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
index 6744d74..20917bd 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
@@ -21,9 +21,12 @@
 import com.android.systemui.appops.AppOpsControllerImpl;
 import com.android.systemui.classifier.FalsingManagerProxy;
 import com.android.systemui.doze.DozeHost;
+import com.android.systemui.globalactions.GlobalActionsComponent;
+import com.android.systemui.globalactions.GlobalActionsImpl;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.PowerNotificationWarnings;
@@ -99,6 +102,17 @@
     /**
      */
     @Binds
+    public abstract GlobalActions provideGlobalActions(GlobalActionsImpl controllerImpl);
+
+    /**
+     */
+    @Binds
+    public abstract GlobalActions.GlobalActionsManager provideGlobalActionsManager(
+            GlobalActionsComponent controllerImpl);
+
+    /**
+     */
+    @Binds
     public abstract LocationController provideLocationController(
             LocationControllerImpl controllerImpl);
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index 26337b1..3aa14a3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -26,17 +26,24 @@
 import android.app.NotificationManager;
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
+import android.app.trust.TrustManager;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.IPackageManager;
 import android.content.res.Resources;
 import android.hardware.SensorPrivacyManager;
+import android.media.AudioManager;
+import android.net.ConnectivityManager;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.Vibrator;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
 import android.view.IWindowManager;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
@@ -66,6 +73,12 @@
         return context.getSystemService(AccessibilityManager.class);
     }
 
+    @Provides
+    @Singleton
+    static ActivityManager provideActivityManager(Context context) {
+        return context.getSystemService(ActivityManager.class);
+    }
+
     @Singleton
     @Provides
     static AlarmManager provideAlarmManager(Context context) {
@@ -74,10 +87,21 @@
 
     @Provides
     @Singleton
-    static ActivityManager provideActivityManager(Context context) {
-        return context.getSystemService(ActivityManager.class);
+    static AudioManager provideAudioManager(Context context) {
+        return context.getSystemService(AudioManager.class);
     }
 
+    @Provides
+    @Singleton
+    static ConnectivityManager provideConnectivityManagager(Context context) {
+        return context.getSystemService(ConnectivityManager.class);
+    }
+
+    @Provides
+    @Singleton
+    static ContentResolver provideContentResolver(Context context) {
+        return context.getContentResolver();
+    }
 
     @Provides
     @DisplayId
@@ -185,6 +209,31 @@
 
     @Provides
     @Singleton
+    static TelecomManager provideTelecomManager(Context context) {
+        return context.getSystemService(TelecomManager.class);
+    }
+
+    @Provides
+    @Singleton
+    static TelephonyManager provideTelephonyManager(Context context) {
+        return context.getSystemService(TelephonyManager.class);
+    }
+
+    @Provides
+    @Singleton
+    static TrustManager provideTrustManager(Context context) {
+        return context.getSystemService(TrustManager.class);
+    }
+
+    @Provides
+    @Singleton
+    @Nullable
+    static Vibrator provideVibrator(Context context) {
+        return context.getSystemService(Vibrator.class);
+    }
+
+    @Provides
+    @Singleton
     static UserManager provideUserManager(Context context) {
         return context.getSystemService(UserManager.class);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 58ddda9..b195238 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -31,6 +31,8 @@
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
 import com.android.systemui.statusbar.notification.people.PeopleHubModule;
 import com.android.systemui.statusbar.phone.KeyguardLiftController;
@@ -82,6 +84,11 @@
                 keyguardUpdateMonitor, dumpController);
     }
 
+    /** */
+    @Binds
+    public abstract NotificationRowBinder bindNotificationRowBinder(
+            NotificationRowBinderImpl notificationRowBinder);
+
     @Singleton
     @Provides
     static SysUiState provideSysUiState() {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index c9faf69..4e88726 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -91,7 +91,7 @@
         if (mDebuggable) {
             IntentFilter filter = new IntentFilter();
             filter.addAction(ACTION_AOD_BRIGHTNESS);
-            mBroadcastDispatcher.registerReceiver(this, filter, handler, UserHandle.ALL);
+            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, handler, UserHandle.ALL);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index 19b6f82..e949007 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -19,7 +19,6 @@
 import android.os.ServiceManager;
 
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.Dependency;
 import com.android.systemui.SystemUI;
 import com.android.systemui.plugins.GlobalActions;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
@@ -29,6 +28,7 @@
 import com.android.systemui.statusbar.policy.ExtensionController.Extension;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 import javax.inject.Singleton;
 
 /**
@@ -38,23 +38,29 @@
 public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager {
 
     private final CommandQueue mCommandQueue;
+    private final ExtensionController mExtensionController;
+    private final Provider<GlobalActions> mGlobalActionsProvider;
     private GlobalActions mPlugin;
     private Extension<GlobalActions> mExtension;
     private IStatusBarService mBarService;
 
     @Inject
-    public GlobalActionsComponent(Context context, CommandQueue commandQueue) {
+    public GlobalActionsComponent(Context context, CommandQueue commandQueue,
+            ExtensionController extensionController,
+            Provider<GlobalActions> globalActionsProvider) {
         super(context);
         mCommandQueue = commandQueue;
+        mExtensionController = extensionController;
+        mGlobalActionsProvider = globalActionsProvider;
     }
 
     @Override
     public void start() {
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
-        mExtension = Dependency.get(ExtensionController.class).newExtension(GlobalActions.class)
+        mExtension = mExtensionController.newExtension(GlobalActions.class)
                 .withPlugin(GlobalActions.class)
-                .withDefault(() -> new GlobalActionsImpl(mContext, mCommandQueue))
+                .withDefault(mGlobalActionsProvider::get)
                 .withCallback(this::onExtensionCallback)
                 .build();
         mPlugin = mExtension.get();
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 3ccad64..fc19fa0 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -21,8 +21,10 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.Dialog;
+import android.app.IActivityManager;
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
@@ -30,11 +32,13 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.UserInfo;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
@@ -44,13 +48,11 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.Vibrator;
 import android.provider.Settings;
-import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
 import android.sysprop.TelephonyProperties;
 import android.telecom.TelecomManager;
@@ -92,6 +94,7 @@
 import com.android.systemui.MultiListLayout.MultiListAdapter;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
 import com.android.systemui.plugins.GlobalActionsPanelPlugin;
@@ -106,6 +109,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.inject.Inject;
+
 /**
  * Helper to show the global actions dialog.  Each item is an {@link Action} that
  * may show depending on whether the keyguard is showing, and whether the device
@@ -146,6 +151,13 @@
     private final LockPatternUtils mLockPatternUtils;
     private final KeyguardManager mKeyguardManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final ContentResolver mContentResolver;
+    private final Resources mResources;
+    private final UserManager mUserManager;
+    private final TrustManager mTrustManager;
+    private final IActivityManager mIActivityManager;
+    private final TelecomManager mTelecomManager;
+    private final MetricsLogger mMetricsLogger;
 
     private ArrayList<Action> mItems;
     private ActionsDialog mDialog;
@@ -161,8 +173,6 @@
     private boolean mIsWaitingForEcmExit = false;
     private boolean mHasTelephony;
     private boolean mHasVibrator;
-    private boolean mHasLogoutButton;
-    private boolean mHasLockdownButton;
     private final boolean mShowSilentToggle;
     private final EmergencyAffordanceManager mEmergencyAffordanceManager;
     private final ScreenshotHelper mScreenshotHelper;
@@ -173,17 +183,32 @@
     /**
      * @param context everything needs a context :(
      */
-    public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs) {
+    @Inject
+    public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs,
+            AudioManager audioManager, IDreamManager iDreamManager,
+            DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils,
+            KeyguardManager keyguardManager, BroadcastDispatcher broadcastDispatcher,
+            ConnectivityManager connectivityManager, TelephonyManager telephonyManager,
+            ContentResolver contentResolver, @Nullable Vibrator vibrator, @Main Resources resources,
+            ConfigurationController configurationController, ActivityStarter activityStarter,
+            KeyguardStateController keyguardStateController, UserManager userManager,
+            TrustManager trustManager, IActivityManager iActivityManager,
+            TelecomManager telecomManager, MetricsLogger metricsLogger) {
         mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
         mWindowManagerFuncs = windowManagerFuncs;
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        mDreamManager = IDreamManager.Stub.asInterface(
-                ServiceManager.getService(DreamService.DREAM_SERVICE));
-        mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
-                Context.DEVICE_POLICY_SERVICE);
-        mLockPatternUtils = new LockPatternUtils(mContext);
-        mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-        mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
+        mAudioManager = audioManager;
+        mDreamManager = iDreamManager;
+        mDevicePolicyManager = devicePolicyManager;
+        mLockPatternUtils = lockPatternUtils;
+        mKeyguardManager = keyguardManager;
+        mBroadcastDispatcher = broadcastDispatcher;
+        mContentResolver = contentResolver;
+        mResources = resources;
+        mUserManager = userManager;
+        mTrustManager = trustManager;
+        mIActivityManager = iActivityManager;
+        mTelecomManager = telecomManager;
+        mMetricsLogger = metricsLogger;
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -192,32 +217,25 @@
         filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
         mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
 
-        ConnectivityManager cm = (ConnectivityManager)
-                context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+        mHasTelephony = connectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
 
         // get notified of phone state changes
-        TelephonyManager telephonyManager =
-                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
-        mContext.getContentResolver().registerContentObserver(
+        contentResolver.registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
                 mAirplaneModeObserver);
-        Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
         mHasVibrator = vibrator != null && vibrator.hasVibrator();
 
-        mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean(
+        mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean(
                 R.bool.config_useFixedVolume);
 
         mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
         mScreenshotHelper = new ScreenshotHelper(context);
         mScreenRecordHelper = new ScreenRecordHelper(context);
 
-        Dependency.get(ConfigurationController.class).addCallback(this);
+        configurationController.addCallback(this);
 
-        mActivityStarter = Dependency.get(ActivityStarter.class);
-        KeyguardStateController keyguardStateController =
-                Dependency.get(KeyguardStateController.class);
+        mActivityStarter = activityStarter;
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
             public void onUnlockedChanged() {
@@ -343,12 +361,9 @@
         onAirplaneModeChanged();
 
         mItems = new ArrayList<Action>();
-        String[] defaultActions = mContext.getResources().getStringArray(
-                R.array.config_globalActionsList);
+        String[] defaultActions = mResources.getStringArray(R.array.config_globalActionsList);
 
         ArraySet<String> addedKeys = new ArraySet<String>();
-        mHasLogoutButton = false;
-        mHasLockdownButton = false;
         for (int i = 0; i < defaultActions.length; i++) {
             String actionKey = defaultActions[i];
             if (addedKeys.contains(actionKey)) {
@@ -360,7 +375,7 @@
             } else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
                 mItems.add(mAirplaneModeOn);
             } else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
-                if (Settings.Global.getInt(mContext.getContentResolver(),
+                if (Settings.Global.getInt(mContentResolver,
                         Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
                     mItems.add(new BugReportAction());
                 }
@@ -375,11 +390,10 @@
             } else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
                 mItems.add(getSettingsAction());
             } else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
-                if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                if (Settings.Secure.getIntForUser(mContentResolver,
                             Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, getCurrentUser().id) != 0
                         && shouldDisplayLockdown()) {
                     mItems.add(getLockdownAction());
-                    mHasLockdownButton = true;
                 }
             } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
                 mItems.add(getVoiceAssistAction());
@@ -393,7 +407,6 @@
                 if (mDevicePolicyManager.isLogoutEnabled()
                         && getCurrentUser().id != UserHandle.USER_SYSTEM) {
                     mItems.add(new LogoutAction());
-                    mHasLogoutButton = true;
                 }
             } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) {
                 if (!mEmergencyAffordanceManager.needsEmergencyAffordance()) {
@@ -474,8 +487,7 @@
 
         @Override
         public boolean onLongPress() {
-            UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-            if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+            if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
                 mWindowManagerFuncs.reboot(true);
                 return true;
             }
@@ -560,9 +572,8 @@
 
         @Override
         public void onPress() {
-            MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU);
-            Intent intent = mContext.getSystemService(TelecomManager.class)
-                    .createLaunchEmergencyDialerIntent(null /* number */);
+            mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU);
+            Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent(null /* number */);
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
                     | Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -579,8 +590,7 @@
 
         @Override
         public boolean onLongPress() {
-            UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-            if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+            if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
                 mWindowManagerFuncs.reboot(true);
                 return true;
             }
@@ -618,8 +628,7 @@
                 @Override
                 public void run() {
                     mScreenshotHelper.takeScreenshot(1, true, true, mHandler, null);
-                    MetricsLogger.action(mContext,
-                            MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
+                    mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
                 }
             }, 500);
         }
@@ -666,11 +675,11 @@
                 public void run() {
                     try {
                         // Take an "interactive" bugreport.
-                        MetricsLogger.action(mContext,
+                        mMetricsLogger.action(
                                 MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
-                        if (!ActivityManager.getService().launchBugReportHandlerApp()) {
+                        if (!mIActivityManager.launchBugReportHandlerApp()) {
                             Log.w(TAG, "Bugreport handler could not be launched");
-                            ActivityManager.getService().requestInteractiveBugReport();
+                            mIActivityManager.requestInteractiveBugReport();
                         }
                     } catch (RemoteException e) {
                     }
@@ -687,8 +696,8 @@
             }
             try {
                 // Take a "full" bugreport.
-                MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
-                ActivityManager.getService().requestFullBugReport();
+                mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
+                mIActivityManager.requestFullBugReport();
             } catch (RemoteException e) {
             }
             return false;
@@ -726,8 +735,8 @@
             mHandler.postDelayed(() -> {
                 try {
                     int currentUserId = getCurrentUser().id;
-                    ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM);
-                    ActivityManager.getService().stopUser(currentUserId, true /*force*/, null);
+                    mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
+                    mIActivityManager.stopUser(currentUserId, true /*force*/, null);
                 } catch (RemoteException re) {
                     Log.e(TAG, "Couldn't logout user " + re);
                 }
@@ -834,20 +843,18 @@
     }
 
     private void lockProfiles() {
-        final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        final TrustManager tm = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
         final int currentUserId = getCurrentUser().id;
-        final int[] profileIds = um.getEnabledProfileIds(currentUserId);
+        final int[] profileIds = mUserManager.getEnabledProfileIds(currentUserId);
         for (final int id : profileIds) {
             if (id != currentUserId) {
-                tm.setDeviceLockedForUser(id, true);
+                mTrustManager.setDeviceLockedForUser(id, true);
             }
         }
     }
 
     private UserInfo getCurrentUser() {
         try {
-            return ActivityManager.getService().getCurrentUser();
+            return mIActivityManager.getCurrentUser();
         } catch (RemoteException re) {
             return null;
         }
@@ -859,9 +866,8 @@
     }
 
     private void addUsersToMenu(ArrayList<Action> items) {
-        UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        if (um.isUserSwitcherEnabled()) {
-            List<UserInfo> users = um.getUsers();
+        if (mUserManager.isUserSwitcherEnabled()) {
+            List<UserInfo> users = mUserManager.getUsers();
             UserInfo currentUser = getCurrentUser();
             for (final UserInfo user : users) {
                 if (user.supportsSwitchToByUser()) {
@@ -875,7 +881,7 @@
                                     + (isCurrentUser ? " \u2714" : "")) {
                         public void onPress() {
                             try {
-                                ActivityManager.getService().switchUser(user.id);
+                                mIActivityManager.switchUser(user.id);
                             } catch (RemoteException re) {
                                 Log.e(TAG, "Couldn't switch user " + re);
                             }
@@ -932,7 +938,7 @@
 
     /** {@inheritDoc} */
     public void onShow(DialogInterface dialog) {
-        MetricsLogger.visible(mContext, MetricsEvent.POWER_MENU);
+        mMetricsLogger.visible(MetricsEvent.POWER_MENU);
     }
 
     /**
@@ -1492,7 +1498,7 @@
         if (mHasTelephony) return;
 
         boolean airplaneModeOn = Settings.Global.getInt(
-                mContext.getContentResolver(),
+                mContentResolver,
                 Settings.Global.AIRPLANE_MODE_ON,
                 0) == 1;
         mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off;
@@ -1504,7 +1510,7 @@
      */
     private void changeAirplaneModeSystemSetting(boolean on) {
         Settings.Global.putInt(
-                mContext.getContentResolver(),
+                mContentResolver,
                 Settings.Global.AIRPLANE_MODE_ON,
                 on ? 1 : 0);
         Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index d385123..c911bf2 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -45,24 +45,32 @@
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import javax.inject.Inject;
+
+import dagger.Lazy;
+
 public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks,
         PluginListener<GlobalActionsPanelPlugin> {
 
     private static final float SHUTDOWN_SCRIM_ALPHA = 0.95f;
 
     private final Context mContext;
+    private final Lazy<GlobalActionsDialog> mGlobalActionsDialogLazy;
     private final KeyguardStateController mKeyguardStateController;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final ExtensionController.Extension<GlobalActionsPanelPlugin> mPanelExtension;
     private GlobalActionsPanelPlugin mPlugin;
     private final CommandQueue mCommandQueue;
-    private GlobalActionsDialog mGlobalActions;
+    private GlobalActionsDialog mGlobalActionsDialog;
     private boolean mDisabled;
     private final PluginManager mPluginManager;
     private final String mPluginPackageName;
 
-    public GlobalActionsImpl(Context context, CommandQueue commandQueue) {
+    @Inject
+    public GlobalActionsImpl(Context context, CommandQueue commandQueue,
+            Lazy<GlobalActionsDialog> globalActionsDialogLazy) {
         mContext = context;
+        mGlobalActionsDialogLazy = globalActionsDialogLazy;
         mKeyguardStateController = Dependency.get(KeyguardStateController.class);
         mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
         mPluginManager = Dependency.get(PluginManager.class);
@@ -83,19 +91,17 @@
         mCommandQueue.removeCallback(this);
         mPluginManager.removePluginListener(this);
         if (mPlugin != null) mPlugin.onDestroy();
-        if (mGlobalActions != null) {
-            mGlobalActions.destroy();
-            mGlobalActions = null;
+        if (mGlobalActionsDialog != null) {
+            mGlobalActionsDialog.destroy();
+            mGlobalActionsDialog = null;
         }
     }
 
     @Override
     public void showGlobalActions(GlobalActionsManager manager) {
         if (mDisabled) return;
-        if (mGlobalActions == null) {
-            mGlobalActions = new GlobalActionsDialog(mContext, manager);
-        }
-        mGlobalActions.showDialog(mKeyguardStateController.isShowing(),
+        mGlobalActionsDialog = mGlobalActionsDialogLazy.get();
+        mGlobalActionsDialog.showDialog(mKeyguardStateController.isShowing(),
                 mDeviceProvisionedController.isDeviceProvisioned(),
                 mPlugin != null ? mPlugin : mPanelExtension.get());
         Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth();
@@ -189,8 +195,8 @@
         final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0;
         if (displayId != mContext.getDisplayId() || disabled == mDisabled) return;
         mDisabled = disabled;
-        if (disabled && mGlobalActions != null) {
-            mGlobalActions.dismissDialog();
+        if (disabled && mGlobalActionsDialog != null) {
+            mGlobalActionsDialog.dismissDialog();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 59ac329..48750fa 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -233,7 +233,7 @@
             filter.addAction(Intent.ACTION_SCREEN_OFF);
             filter.addAction(Intent.ACTION_SCREEN_ON);
             filter.addAction(Intent.ACTION_USER_SWITCHED);
-            mBroadcastDispatcher.registerReceiver(this, filter, mHandler);
+            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index bbff117..ad79cad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -322,7 +322,7 @@
         filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
         try {
             mUserReceiverRegistered.set(true);
-            mBroadcastDispatcher.registerReceiver(this, filter, mHandler, mUser);
+            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler, mUser);
         } catch (Exception ex) {
             mUserReceiverRegistered.set(false);
             Log.e(TAG, "Could not register unlock receiver", ex);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
index 077d260..9599d77 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
@@ -98,7 +98,8 @@
             if (!mReceiverRegistered) {
                 mCurrentUserId = ActivityManager.getCurrentUser();
                 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
-                mBroadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
+                mBroadcastDispatcher.registerReceiver(this, filter, null,
+                        UserHandle.ALL);
                 mReceiverRegistered = true;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 43d0399..667e721 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -584,7 +584,15 @@
 
     public void bindRow(ExpandableNotificationRow row) {
         row.setRemoteInputController(mRemoteInputController);
-        row.setRemoteViewClickHandler(mOnClickHandler);
+    }
+
+    /**
+     * Return on-click handler for notification remote views
+     *
+     * @return on-click handler
+     */
+    public RemoteViews.OnClickHandler getRemoteViewsOnClickHandler() {
+        return mOnClickHandler;
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
index d1f6ebf..ec8dbea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 
+import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+
 import javax.inject.Singleton;
 
 import dagger.Module;
@@ -26,7 +28,7 @@
 /**
  * Dagger Module providing common dependencies of StatusBar.
  */
-@Module
+@Module(includes = {NotificationRowModule.class})
 public class StatusBarDependenciesModule {
     /**
      * Provides our instance of CommandQueue which is considered optional.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index dc84b57..9c626f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -307,6 +307,7 @@
             case Icon.TYPE_RESOURCE:
                 return a.getResPackage().equals(b.getResPackage()) && a.getResId() == b.getResId();
             case Icon.TYPE_URI:
+            case Icon.TYPE_URI_ADAPTIVE_BITMAP:
                 return a.getUriString().equals(b.getUriString());
             default:
                 return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index a2578ab..36dcaac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -32,7 +32,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
@@ -68,6 +67,8 @@
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
+import dagger.Lazy;
+
 /**
  * NotificationEntryManager is responsible for the adding, removing, and updating of
  * {@link NotificationEntry}s. It also handles tasks such as their inflation and their interaction
@@ -126,8 +127,9 @@
             new ArrayMap<>();
 
     // Lazily retrieved dependencies
-    private NotificationRemoteInputManager mRemoteInputManager;
-    private NotificationRowBinder mNotificationRowBinder;
+    private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy;
+    private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
+    private final LeakDetector mLeakDetector;
 
     private final KeyguardEnvironment mKeyguardEnvironment;
     private final NotificationGroupManager mGroupManager;
@@ -173,12 +175,18 @@
             NotificationGroupManager groupManager,
             NotificationRankingManager rankingManager,
             KeyguardEnvironment keyguardEnvironment,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            Lazy<NotificationRowBinder> notificationRowBinderLazy,
+            Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
+            LeakDetector leakDetector) {
         mNotifLog = notifLog;
         mGroupManager = groupManager;
         mRankingManager = rankingManager;
         mKeyguardEnvironment = keyguardEnvironment;
         mFeatureFlags = featureFlags;
+        mNotificationRowBinderLazy = notificationRowBinderLazy;
+        mRemoteInputManagerLazy = notificationRemoteInputManagerLazy;
+        mLeakDetector = leakDetector;
     }
 
     /** Once called, the NEM will start processing notification events from system server. */
@@ -204,20 +212,6 @@
         mRemoveInterceptor = interceptor;
     }
 
-    /**
-     * Our dependencies can have cyclic references, so some need to be lazy
-     */
-    private NotificationRemoteInputManager getRemoteInputManager() {
-        if (mRemoteInputManager == null) {
-            mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
-        }
-        return mRemoteInputManager;
-    }
-
-    public void setRowBinder(NotificationRowBinder notificationRowBinder) {
-        mNotificationRowBinder = notificationRowBinder;
-    }
-
     public void setUpWithPresenter(NotificationPresenter presenter,
             NotificationListContainer listContainer,
             HeadsUpManager headsUpManager) {
@@ -468,7 +462,7 @@
                 handleGroupSummaryRemoved(key);
                 removeVisibleNotification(key);
                 updateNotifications("removeNotificationInternal");
-                Dependency.get(LeakDetector.class).trackGarbage(entry);
+                mLeakDetector.trackGarbage(entry);
                 removedByUser |= entryDismissed;
 
                 mNotifLog.log(NotifEvent.NOTIF_REMOVED, entry.getSbn(),
@@ -507,8 +501,8 @@
                 boolean isForeground = (entry.getSbn().getNotification().flags
                         & Notification.FLAG_FOREGROUND_SERVICE) != 0;
                 boolean keepForReply =
-                        getRemoteInputManager().shouldKeepForRemoteInputHistory(childEntry)
-                        || getRemoteInputManager().shouldKeepForSmartReplyHistory(childEntry);
+                        mRemoteInputManagerLazy.get().shouldKeepForRemoteInputHistory(childEntry)
+                        || mRemoteInputManagerLazy.get().shouldKeepForSmartReplyHistory(childEntry);
                 if (isForeground || keepForReply) {
                     // the child is a foreground service notification which we can't remove or it's
                     // a child we're keeping around for reply!
@@ -536,12 +530,13 @@
 
         NotificationEntry entry = new NotificationEntry(notification, ranking);
 
-        Dependency.get(LeakDetector.class).trackInstance(entry);
+        mLeakDetector.trackInstance(entry);
 
         // Construct the expanded view.
         if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
-            requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
-                    REASON_CANCEL));
+            mNotificationRowBinderLazy.get()
+                    .inflateViews(entry, () -> performRemoveNotification(notification,
+                            REASON_CANCEL));
         }
 
         abortExistingInflation(key, "addNotification");
@@ -586,15 +581,16 @@
         }
 
         if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
-            requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
-                    REASON_CANCEL));
+            mNotificationRowBinderLazy.get()
+                    .inflateViews(entry, () -> performRemoveNotification(notification,
+                            REASON_CANCEL));
         }
 
         updateNotifications("updateNotificationInternal");
 
         if (DEBUG) {
             // Is this for you?
-            boolean isForCurrentUser = Dependency.get(KeyguardEnvironment.class)
+            boolean isForCurrentUser = mKeyguardEnvironment
                     .isNotificationForCurrentProfiles(notification);
             Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
         }
@@ -644,11 +640,12 @@
 
         // By comparing the old and new UI adjustments, reinflate the view accordingly.
         for (NotificationEntry entry : entries) {
-            requireBinder().onNotificationRankingUpdated(
-                    entry,
-                    oldImportances.get(entry.getKey()),
-                    oldAdjustments.get(entry.getKey()),
-                    NotificationUiAdjustment.extractFromNotificationEntry(entry));
+            mNotificationRowBinderLazy.get()
+                    .onNotificationRankingUpdated(
+                            entry,
+                            oldImportances.get(entry.getKey()),
+                            oldAdjustments.get(entry.getKey()),
+                            NotificationUiAdjustment.extractFromNotificationEntry(entry));
         }
 
         updateNotifications("updateNotificationRanking");
@@ -728,14 +725,6 @@
         }
     }
 
-    private NotificationRowBinder requireBinder() {
-        if (mNotificationRowBinder == null) {
-            throw new RuntimeException("You must initialize NotificationEntryManager by calling"
-                    + "setRowBinder() before using.");
-        }
-        return mNotificationRowBinder;
-    }
-
     /*
      * -----
      * Annexed from NotificationData below:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
index ec1efa5..b960b42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
@@ -24,7 +24,6 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
-import java.util.Objects;
 
 /**
  * Represents a set of grouped notifications. The final notification list is usually a mix of
@@ -58,22 +57,15 @@
 
     @VisibleForTesting
     public void setSummary(@Nullable NotificationEntry summary) {
-        if (!Objects.equals(mSummary, summary)) {
-            mSummary = summary;
-            onGroupingUpdated();
-        }
+        mSummary = summary;
     }
 
     void clearChildren() {
-        if (mChildren.size() != 0) {
-            mChildren.clear();
-            onGroupingUpdated();
-        }
+        mChildren.clear();
     }
 
     void addChild(NotificationEntry child) {
         mChildren.add(child);
-        onGroupingUpdated();
     }
 
     void sortChildren(Comparator<? super NotificationEntry> c) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index 601b3e0..dc68c4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -18,38 +18,20 @@
 
 import android.annotation.Nullable;
 
-import com.android.systemui.Dependency;
-import com.android.systemui.statusbar.notification.collection.provider.DerivedMember;
-import com.android.systemui.statusbar.notification.collection.provider.IsHighPriorityProvider;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-
 /**
  * Abstract superclass for top-level entries, i.e. things that can appear in the final notification
  * list shown to users. In practice, this means either GroupEntries or NotificationEntries.
  */
 public abstract class ListEntry {
     private final String mKey;
-    private final IsHighPriorityProvider mIsHighPriorityProvider = new IsHighPriorityProvider();
-    private final List<DerivedMember> mDerivedMemberList = Arrays.asList(mIsHighPriorityProvider);
 
     @Nullable private GroupEntry mParent;
     @Nullable private GroupEntry mPreviousParent;
     private int mSection;
     int mFirstAddedIteration = -1;
 
-    // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic
-    //  replaced in GroupEntry and NotifListBuilderImpl
-    private final NotificationGroupManager mGroupManager;
-
     ListEntry(String key) {
         mKey = key;
-
-        // TODO: (b/145659174) remove
-        mGroupManager = Dependency.get(NotificationGroupManager.class);
     }
 
     public String getKey() {
@@ -68,11 +50,7 @@
     }
 
     void setParent(@Nullable GroupEntry parent) {
-        if (!Objects.equals(mParent, parent)) {
-            invalidateParent();
-            mParent = parent;
-            onGroupingUpdated();
-        }
+        mParent = parent;
     }
 
     @Nullable public GroupEntry getPreviousParent() {
@@ -91,58 +69,4 @@
     void setSection(int section) {
         mSection = section;
     }
-
-    /**
-     * Resets the cached values of DerivedMembers.
-     */
-    void invalidateDerivedMembers() {
-        for (int i = 0; i < mDerivedMemberList.size(); i++) {
-            mDerivedMemberList.get(i).invalidate();
-        }
-    }
-
-    /**
-     * Whether this notification is shown to the user as a high priority notification: visible on
-     * the lock screen/status bar and in the top section in the shade.
-     */
-    public boolean isHighPriority() {
-        return mIsHighPriorityProvider.get(this);
-    }
-
-    private void invalidateParent() {
-        // invalidate our parent (GroupEntry) since DerivedMembers may be dependent on children
-        if (getParent() != null) {
-            getParent().invalidateDerivedMembers();
-        }
-
-        // TODO: (b/145659174) remove
-        final NotificationEntry notifEntry = getRepresentativeEntry();
-        if (notifEntry != null && mGroupManager.isGroupChild(notifEntry.getSbn())) {
-            NotificationEntry summary = mGroupManager.getLogicalGroupSummary(notifEntry.getSbn());
-            if (summary != null) {
-                summary.invalidateDerivedMembers();
-            }
-        }
-    }
-
-    void onGroupingUpdated() {
-        for (int i = 0; i < mDerivedMemberList.size(); i++) {
-            mDerivedMemberList.get(i).onGroupingUpdated();
-        }
-        invalidateParent();
-    }
-
-    void onSbnUpdated() {
-        for (int i = 0; i < mDerivedMemberList.size(); i++) {
-            mDerivedMemberList.get(i).onSbnUpdated();
-        }
-        invalidateParent();
-    }
-
-    void onRankingUpdated() {
-        for (int i = 0; i < mDerivedMemberList.size(); i++) {
-            mDerivedMemberList.get(i).onRankingUpdated();
-        }
-        invalidateParent();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 28e486d..7301fe1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -204,11 +204,8 @@
                     + " doesn't match existing key " + mKey);
         }
 
-        if (!Objects.equals(mSbn, sbn)) {
-            mSbn = sbn;
-            mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
-            onSbnUpdated();
-        }
+        mSbn = sbn;
+        mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
     }
 
     /**
@@ -233,10 +230,7 @@
                     + " doesn't match existing key " + mKey);
         }
 
-        if (!Objects.equals(mRanking, ranking)) {
-            mRanking = ranking;
-            onRankingUpdated();
-        }
+        mRanking = ranking;
     }
 
     /*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index 7010943..3bbd722 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.notification.NotificationFilter
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
 import com.android.systemui.statusbar.notification.logging.NotifEvent
 import com.android.systemui.statusbar.notification.logging.NotifLog
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
@@ -54,7 +55,8 @@
     private val notifFilter: NotificationFilter,
     private val notifLog: NotifLog,
     sectionsFeatureManager: NotificationSectionsFeatureManager,
-    private val peopleNotificationIdentifier: PeopleNotificationIdentifier
+    private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
+    private val highPriorityProvider: HighPriorityProvider
 ) {
 
     var rankingMap: RankingMap? = null
@@ -81,6 +83,9 @@
         val aHeadsUp = a.isRowHeadsUp
         val bHeadsUp = b.isRowHeadsUp
 
+        val aIsHighPriority = a.isHighPriority()
+        val bIsHighPriority = b.isHighPriority()
+
         when {
             usePeopleFiltering && aIsPeople != bIsPeople -> if (aIsPeople) -1 else 1
             aHeadsUp != bHeadsUp -> if (aHeadsUp) -1 else 1
@@ -90,8 +95,8 @@
             aMedia != bMedia -> if (aMedia) -1 else 1
             // Upsort PRIORITY_MAX system notifications
             aSystemMax != bSystemMax -> if (aSystemMax) -1 else 1
-            a.isHighPriority != b.isHighPriority ->
-                -1 * a.isHighPriority.compareTo(b.isHighPriority)
+            aIsHighPriority != bIsHighPriority ->
+                -1 * aIsHighPriority.compareTo(bIsHighPriority)
             aRank != bRank -> aRank - bRank
             else -> nb.notification.`when`.compareTo(na.notification.`when`)
         }
@@ -154,7 +159,7 @@
     ) {
         if (usePeopleFiltering && entry.isPeopleNotification()) {
             entry.bucket = BUCKET_PEOPLE
-        } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority) {
+        } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority()) {
             entry.bucket = BUCKET_ALERTING
         } else {
             entry.bucket = BUCKET_SILENT
@@ -178,10 +183,6 @@
                         // TODO: notify group manager here?
                         groupManager.onEntryUpdated(entry, oldSbn)
                     }
-
-                    // TODO: (b/145659174) remove after moving to new NotifPipeline
-                    // (should be able to remove all groupManager code post-migration)
-                    entry.invalidateDerivedMembers()
                 }
             }
         }
@@ -191,6 +192,9 @@
             sbn.isPeopleNotification()
     private fun StatusBarNotification.isPeopleNotification() =
             peopleNotificationIdentifier.isPeopleNotification(this)
+
+    private fun NotificationEntry.isHighPriority() =
+            highPriorityProvider.isHighPriority(this)
 }
 
 // Convenience functions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
index 6dc647d..80b5b8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection;
 
+import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 
@@ -29,7 +30,6 @@
 import android.view.ViewGroup;
 
 import com.android.internal.util.NotificationMessagingUtil;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -41,8 +41,8 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -52,57 +52,67 @@
 
 import java.util.Objects;
 
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
 /** Handles inflating and updating views for notifications. */
+@Singleton
 public class NotificationRowBinderImpl implements NotificationRowBinder {
 
     private static final String TAG = "NotificationViewManager";
 
-    private final NotificationGroupManager mGroupManager =
-            Dependency.get(NotificationGroupManager.class);
-    private final NotificationGutsManager mGutsManager =
-            Dependency.get(NotificationGutsManager.class);
-    private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
-            Dependency.get(NotificationInterruptionStateProvider.class);
+    private final NotificationGroupManager mGroupManager;
+    private final NotificationGutsManager mGutsManager;
+    private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
 
     private final Context mContext;
+    private final NotificationRowContentBinder mRowContentBinder;
     private final NotificationMessagingUtil mMessagingUtil;
     private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
             this::logNotificationExpansion;
+    private final NotificationRemoteInputManager mNotificationRemoteInputManager;
+    private final NotificationLockscreenUserManager mNotificationLockscreenUserManager;
     private final boolean mAllowLongPress;
     private final KeyguardBypassController mKeyguardBypassController;
     private final StatusBarStateController mStatusBarStateController;
 
-    private NotificationRemoteInputManager mRemoteInputManager;
     private NotificationPresenter mPresenter;
     private NotificationListContainer mListContainer;
     private HeadsUpManager mHeadsUpManager;
-    private NotificationContentInflater.InflationCallback mInflationCallback;
+    private NotificationRowContentBinder.InflationCallback mInflationCallback;
     private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
     private BindRowCallback mBindRowCallback;
     private NotificationClicker mNotificationClicker;
     private final NotificationLogger mNotificationLogger;
 
+    @Inject
     public NotificationRowBinderImpl(
             Context context,
-            boolean allowLongPress,
+            NotificationRemoteInputManager notificationRemoteInputManager,
+            NotificationLockscreenUserManager notificationLockscreenUserManager,
+            NotificationRowContentBinder rowContentBinder,
+            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
             KeyguardBypassController keyguardBypassController,
             StatusBarStateController statusBarStateController,
+            NotificationGroupManager notificationGroupManager,
+            NotificationGutsManager notificationGutsManager,
+            NotificationInterruptionStateProvider notificationInterruptionStateProvider,
             NotificationLogger logger) {
         mContext = context;
+        mRowContentBinder = rowContentBinder;
         mMessagingUtil = new NotificationMessagingUtil(context);
+        mNotificationRemoteInputManager = notificationRemoteInputManager;
+        mNotificationLockscreenUserManager = notificationLockscreenUserManager;
         mAllowLongPress = allowLongPress;
         mKeyguardBypassController = keyguardBypassController;
         mStatusBarStateController = statusBarStateController;
+        mGroupManager = notificationGroupManager;
+        mGutsManager = notificationGutsManager;
+        mNotificationInterruptionStateProvider = notificationInterruptionStateProvider;
         mNotificationLogger = logger;
     }
 
-    private NotificationRemoteInputManager getRemoteInputManager() {
-        if (mRemoteInputManager == null) {
-            mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
-        }
-        return mRemoteInputManager;
-    }
-
     /**
      * Sets up late-bound dependencies for this component.
      */
@@ -117,7 +127,7 @@
         mOnAppOpsClickListener = mGutsManager::openGuts;
     }
 
-    public void setInflationCallback(NotificationContentInflater.InflationCallback callback) {
+    public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) {
         mInflationCallback = callback;
     }
 
@@ -156,19 +166,6 @@
     private void bindRow(NotificationEntry entry, PackageManager pmUser,
             StatusBarNotification sbn, ExpandableNotificationRow row,
             Runnable onDismissRunnable) {
-        row.setExpansionLogger(mExpansionLogger, entry.getSbn().getKey());
-        row.setBypassController(mKeyguardBypassController);
-        row.setStatusBarStateController(mStatusBarStateController);
-        row.setGroupManager(mGroupManager);
-        row.setHeadsUpManager(mHeadsUpManager);
-        row.setOnExpandClickListener(mPresenter);
-        row.setInflationCallback(mInflationCallback);
-        if (mAllowLongPress) {
-            row.setLongPressListener(mGutsManager::openGuts);
-        }
-        mListContainer.bindRow(row);
-        getRemoteInputManager().bindRow(row);
-
         // Get the app name.
         // Note that Notification.Builder#bindHeaderAppName has similar logic
         // but since this field is used in the guts, it must be accurate.
@@ -186,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);
     }
 
@@ -263,8 +278,7 @@
         if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
             row.setInflationFlags(FLAG_CONTENT_VIEW_HEADS_UP);
         }
-        row.setNeedsRedaction(
-                Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry));
+        row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry));
         row.inflateViews();
 
         // bind the click event to the content area
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 9312c22..db107f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -43,6 +43,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
@@ -62,6 +63,7 @@
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final StatusBarStateController mStatusBarStateController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final HighPriorityProvider mHighPriorityProvider;
 
     @Inject
     public KeyguardCoordinator(
@@ -71,15 +73,16 @@
             NotificationLockscreenUserManager lockscreenUserManager,
             BroadcastDispatcher broadcastDispatcher,
             StatusBarStateController statusBarStateController,
-            KeyguardUpdateMonitor keyguardUpdateMonitor) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            HighPriorityProvider highPriorityProvider) {
         mContext = context;
         mMainHandler = mainThreadHandler;
         mKeyguardStateController = keyguardStateController;
         mLockscreenUserManager = lockscreenUserManager;
-
         mBroadcastDispatcher = broadcastDispatcher;
         mStatusBarStateController = statusBarStateController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mHighPriorityProvider = highPriorityProvider;
     }
 
     @Override
@@ -151,7 +154,7 @@
         }
         if (NotificationUtils.useNewInterruptionModel(mContext)
                 && hideSilentNotificationsOnLockscreen()) {
-            return entry.isHighPriority();
+            return mHighPriorityProvider.isHighPriority(entry);
         } else {
             return entry.getRepresentativeEntry() != null
                     && !entry.getRepresentativeEntry().getRanking().isAmbient();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
index eeb54ab..2436bb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
@@ -76,6 +76,7 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println();
         pw.println(TAG + ":");
         for (Coordinator c : mCoordinators) {
             pw.println("\t" + c.getClass());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java
index 069c15f..c3e3c53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java
@@ -259,7 +259,7 @@
         pw.println("Coalesced notifications:");
         for (EventBatch batch : mBatches.values()) {
             pw.println("   Batch " + batch.mGroupKey + ":");
-            pw.println("       Created" + (now - batch.mCreatedTimestamp) + "ms ago");
+            pw.println("       Created " + (now - batch.mCreatedTimestamp) + "ms ago");
             for (CoalescedEvent event : batch.mMembers) {
                 pw.println("       " + event.getKey());
                 eventCount++;
@@ -299,5 +299,5 @@
         void onNotificationBatchPosted(List<CoalescedEvent> events);
     }
 
-    private static final int GROUP_LINGER_DURATION = 40;
+    private static final int GROUP_LINGER_DURATION = 500;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java
deleted file mode 100644
index 815e6f7..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.provider;
-/**
- * Caches a computed value until invalidate() is called
- * @param <Parent> Object used to computeValue
- * @param <Value> type of value to cache until invalidate is called
- */
-public abstract class DerivedMember<Parent, Value> {
-    private Value mValue;
-    protected abstract Value computeValue(Parent parent);
-
-    /**
-     * Gets the last cached value, else recomputes the value.
-     */
-    public Value get(Parent parent) {
-        if (mValue == null) {
-            mValue = computeValue(parent);
-        }
-        return mValue;
-    }
-
-    /**
-     * Resets the cached value.
-     * Next time "get" is called, the value is recomputed.
-     */
-    public void invalidate() {
-        mValue = null;
-    }
-
-    /**
-     * Called when a NotificationEntry's status bar notification has updated.
-     * Derived members can invalidate here.
-     */
-    public void onSbnUpdated() {}
-
-    /**
-     * Called when a NotificationEntry's Ranking has updated.
-     * Derived members can invalidate here.
-     */
-    public void onRankingUpdated() {}
-
-    /**
-     * Called when a ListEntry's grouping information (parent or children) has changed.
-     * Derived members can invalidate here.
-     */
-    public void onGroupingUpdated() {}
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
new file mode 100644
index 0000000..3cc5e62
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.provider;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+
+import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Determines whether a notification is considered 'high priority'.
+ *
+ * Notifications that are high priority are visible on the lock screen/status bar and in the top
+ * section in the shade.
+ */
+@Singleton
+public class HighPriorityProvider {
+    private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+
+    @Inject
+    public HighPriorityProvider(PeopleNotificationIdentifier peopleNotificationIdentifier) {
+        mPeopleNotificationIdentifier = peopleNotificationIdentifier;
+    }
+
+    /**
+     * @return true if the ListEntry is high priority, else false
+     *
+     * A NotificationEntry is considered high priority if it:
+     *  - has importance greater than or equal to IMPORTANCE_DEFAULT
+     *  OR
+     *  - their importance has NOT been set to a low priority option by the user AND the
+     *  notification fulfills one of the following:
+     *      - has a person associated with it
+     *      - has a media session associated with it
+     *      - has messaging style
+     *
+     * A GroupEntry is considered high priority if its representativeEntry (summary) or children are
+     * high priority
+     */
+    public boolean isHighPriority(ListEntry entry) {
+        if (entry == null) {
+            return false;
+        }
+
+        final NotificationEntry notifEntry = entry.getRepresentativeEntry();
+        return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT
+                || hasHighPriorityCharacteristics(notifEntry)
+                || hasHighPriorityChild(entry);
+    }
+
+
+    private boolean hasHighPriorityChild(ListEntry entry) {
+        if (entry instanceof GroupEntry) {
+            for (NotificationEntry child : ((GroupEntry) entry).getChildren()) {
+                if (isHighPriority(child)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private boolean hasHighPriorityCharacteristics(NotificationEntry entry) {
+        return !hasUserSetImportance(entry)
+                && (isImportantOngoing(entry)
+                || entry.getSbn().getNotification().hasMediaSession()
+                || isPeopleNotification(entry)
+                || isMessagingStyle(entry));
+    }
+
+    private boolean isImportantOngoing(NotificationEntry entry) {
+        return entry.getSbn().getNotification().isForegroundService()
+                && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW;
+    }
+
+    private boolean isMessagingStyle(NotificationEntry entry) {
+        return Notification.MessagingStyle.class.equals(
+                entry.getSbn().getNotification().getNotificationStyle());
+    }
+
+    private boolean isPeopleNotification(NotificationEntry entry) {
+        return mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn());
+    }
+
+    private boolean hasUserSetImportance(NotificationEntry entry) {
+        return entry.getRanking().getChannel() != null
+                && entry.getRanking().getChannel().hasUserSetImportance();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java
deleted file mode 100644
index 76e256b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.provider;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.Person;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Whether the ListEntry is shown to the user as a high priority notification: visible on
- * the lock screen/status bar and in the top section in the shade.
- *
- * A NotificationEntry is considered high priority if it:
- *  - has importance greater than or equal to IMPORTANCE_DEFAULT
- *  OR
- *  - their importance has NOT been set to a low priority option by the user AND the notification
- *  fulfills one of the following:
- *      - has a person associated with it
- *      - has a media session associated with it
- *      - has messaging style
- *
- * A GroupEntry is considered high priority if its representativeEntry (summary) or children are
- * high priority
- */
-public class IsHighPriorityProvider extends DerivedMember<ListEntry, Boolean> {
-    // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic
-    //  replaced in GroupEntry and NotifListBuilderImpl
-    private final NotificationGroupManager mGroupManager;
-
-
-    public IsHighPriorityProvider() {
-        // TODO: (b/145659174) remove
-        mGroupManager = Dependency.get(NotificationGroupManager.class);
-    }
-
-    @Override
-    protected Boolean computeValue(ListEntry entry) {
-        if (entry == null) {
-            return false;
-        }
-
-        return isHighPriority(entry);
-    }
-
-    private boolean isHighPriority(ListEntry listEntry) {
-        // requires groups have been set (AFTER PipelineState.STATE_TRANSFORMING)
-        final NotificationEntry notifEntry = listEntry.getRepresentativeEntry();
-        return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT
-                || hasHighPriorityCharacteristics(notifEntry)
-                || hasHighPriorityChild(listEntry);
-
-    }
-
-    private boolean hasHighPriorityChild(ListEntry entry) {
-        // TODO: (b/145659174) remove
-        if (entry instanceof NotificationEntry) {
-            NotificationEntry notifEntry = (NotificationEntry) entry;
-            if (mGroupManager.isSummaryOfGroup(notifEntry.getSbn())) {
-                List<NotificationEntry> logicalChildren =
-                        mGroupManager.getLogicalChildren(notifEntry.getSbn());
-                for (NotificationEntry child : logicalChildren) {
-                    if (child.isHighPriority()) {
-                        return true;
-                    }
-                }
-            }
-        }
-
-        if (entry instanceof GroupEntry) {
-            for (NotificationEntry child : ((GroupEntry) entry).getChildren()) {
-                if (child.isHighPriority()) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private boolean hasHighPriorityCharacteristics(NotificationEntry entry) {
-        return !hasUserSetImportance(entry)
-                && (isImportantOngoing(entry)
-                || entry.getSbn().getNotification().hasMediaSession()
-                || hasPerson(entry)
-                || isMessagingStyle(entry));
-    }
-
-    private boolean isImportantOngoing(NotificationEntry entry) {
-        return entry.getSbn().getNotification().isForegroundService()
-                && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW;
-    }
-
-    private boolean isMessagingStyle(NotificationEntry entry) {
-        return Notification.MessagingStyle.class.equals(
-                entry.getSbn().getNotification().getNotificationStyle());
-    }
-
-    private boolean hasPerson(NotificationEntry entry) {
-        // TODO: cache favorite and recent contacts to check contact affinity
-        Notification notification = entry.getSbn().getNotification();
-        ArrayList<Person> people = notification.extras != null
-                ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST)
-                : new ArrayList<>();
-        return people != null && !people.isEmpty();
-    }
-
-    private boolean hasUserSetImportance(NotificationEntry entry) {
-        return entry.getRanking().getChannel() != null
-                && entry.getRanking().getChannel().hasUserSetImportance();
-    }
-
-    @Override
-    public void onSbnUpdated() {
-        invalidate();
-    }
-
-    @Override
-    public void onRankingUpdated() {
-        invalidate();
-    }
-
-    @Override
-    public void onGroupingUpdated() {
-        invalidate();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/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/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 6f2abba..779a224 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -53,6 +53,7 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -81,6 +82,7 @@
     private final Context mContext;
     private final VisualStabilityManager mVisualStabilityManager;
     private final AccessibilityManager mAccessibilityManager;
+    private final HighPriorityProvider mHighPriorityProvider;
 
     // Dependencies:
     private final NotificationLockscreenUserManager mLockscreenUserManager =
@@ -109,12 +111,14 @@
     @Inject
     public NotificationGutsManager(Context context, VisualStabilityManager visualStabilityManager,
             Lazy<StatusBar> statusBarLazy, @Main Handler mainHandler,
-            AccessibilityManager accessibilityManager) {
+            AccessibilityManager accessibilityManager,
+            HighPriorityProvider highPriorityProvider) {
         mContext = context;
         mVisualStabilityManager = visualStabilityManager;
         mStatusBarLazy = statusBarLazy;
         mMainHandler = mainHandler;
         mAccessibilityManager = accessibilityManager;
+        mHighPriorityProvider = highPriorityProvider;
     }
 
     public void setUpWithPresenter(NotificationPresenter presenter,
@@ -331,8 +335,7 @@
                 row.getIsNonblockable(),
                 isForBlockingHelper,
                 row.getEntry().getImportance(),
-                row.getEntry().isHighPriority());
-
+                mHighPriorityProvider.isHighPriority(row.getEntry()));
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index b4ccb56..edfd1b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -46,6 +46,7 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
 import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent;
+import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 
 import java.util.ArrayList;
@@ -269,7 +270,8 @@
         }
         mAppOpsItem = createAppOpsItem(mContext);
         if (mIsUsingBidirectionalSwipe) {
-            mInfoItem = createInfoItem(mContext, !mParent.getEntry().isHighPriority());
+            mInfoItem = createInfoItem(mContext,
+                    mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_SILENT);
         } else {
             mInfoItem = createInfoItem(mContext);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/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/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index a3b1b5f..a6842ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -366,8 +366,8 @@
         IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
         filter.addAction(Intent.ACTION_SCREEN_ON);
         filter.addAction(Intent.ACTION_USER_SWITCHED);
-        mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, Handler.getMain(),
-                UserHandle.ALL);
+        mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter,
+                Handler.getMain(), UserHandle.ALL);
         notifyNavigationBarScreenOn();
 
         mOverviewProxyService.addCallback(mOverviewProxyListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index fe0739f..896b6e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -32,7 +32,6 @@
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.AsyncInflationTask;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
@@ -428,7 +427,7 @@
          * The notification is still pending inflation but we've decided that we no longer need
          * the content view (e.g. suppression might have changed and we decided we need to transfer
          * back). However, there is no way to abort just this inflation if other inflation requests
-         * have started (see {@link AsyncInflationTask#supersedeTask(InflationTask)}). So instead
+         * have started (see {@link InflationTask#supersedeTask(InflationTask)}). So instead
          * we just flag it as aborted and free when it's inflated.
          */
         boolean mAbortOnInflation;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 5b34aa7..00e38f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -179,7 +179,7 @@
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
-        broadcastDispatcher.registerReceiver(mIntentReceiver, filter, mHandler);
+        broadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);
 
         // listen for user / profile change.
         try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index ccc86b1..189d3b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -29,7 +29,6 @@
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
 
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
@@ -365,7 +364,6 @@
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final BypassHeadsUpNotifier mBypassHeadsUpNotifier;
-    private final boolean mAllowNotificationLongPress;
     private final Lazy<NewNotifPipeline> mNewNotifPipeline;
     private final FalsingManager mFalsingManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
@@ -388,6 +386,7 @@
     private final KeyguardDismissUtil mKeyguardDismissUtil;
     private final ExtensionController mExtensionController;
     private final UserInfoControllerImpl mUserInfoControllerImpl;
+    private final NotificationRowBinderImpl mNotificationRowBinder;
     private final DismissCallbackRegistry mDismissCallbackRegistry;
 
     // expanded notifications
@@ -626,7 +625,6 @@
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
             BypassHeadsUpNotifier bypassHeadsUpNotifier,
-            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
             Lazy<NewNotifPipeline> newNotifPipeline,
             FalsingManager falsingManager,
             BroadcastDispatcher broadcastDispatcher,
@@ -693,6 +691,7 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
+            NotificationRowBinderImpl notificationRowBinder,
             DismissCallbackRegistry dismissCallbackRegistry) {
         super(context);
         mFeatureFlags = featureFlags;
@@ -707,7 +706,6 @@
         mHeadsUpManager = headsUpManagerPhone;
         mDynamicPrivacyController = dynamicPrivacyController;
         mBypassHeadsUpNotifier = bypassHeadsUpNotifier;
-        mAllowNotificationLongPress = allowNotificationLongPress;
         mNewNotifPipeline = newNotifPipeline;
         mFalsingManager = falsingManager;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -772,6 +770,7 @@
         mKeyguardDismissUtil = keyguardDismissUtil;
         mExtensionController = extensionController;
         mUserInfoControllerImpl = userInfoControllerImpl;
+        mNotificationRowBinder = notificationRowBinder;
         mDismissCallbackRegistry = dismissCallbackRegistry;
 
         mBubbleExpandListener =
@@ -1237,19 +1236,11 @@
                 mStatusBarWindowViewController, this, mNotificationPanelViewController,
                 (NotificationListContainer) mStackScroller);
 
-        final NotificationRowBinderImpl rowBinder =
-                new NotificationRowBinderImpl(
-                        mContext,
-                        mAllowNotificationLongPress,
-                        mKeyguardBypassController,
-                        mStatusBarStateController,
-                        mNotificationLogger);
-
         // TODO: inject this.
         mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController,
                 mHeadsUpManager, mStatusBarWindow, mStackScroller, mDozeScrimController,
                 mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController,
-                mNotificationAlertingManager, rowBinder, mKeyguardStateController,
+                mNotificationAlertingManager, mNotificationRowBinder, mKeyguardStateController,
                 mKeyguardIndicationController,
                 this /* statusBar */, mShadeController, mCommandQueue, mInitController);
 
@@ -1273,20 +1264,19 @@
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
 
         if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
-            mEntryManager.setRowBinder(rowBinder);
-            rowBinder.setInflationCallback(mEntryManager);
+            mNotificationRowBinder.setInflationCallback(mEntryManager);
         }
 
         mRemoteInputUriController.attach(mEntryManager);
 
-        rowBinder.setNotificationClicker(new NotificationClicker(
+        mNotificationRowBinder.setNotificationClicker(new NotificationClicker(
                 Optional.of(this), mBubbleController, mNotificationActivityStarter));
 
         mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager);
         mNotificationListController.bind();
 
         if (mFeatureFlags.isNewNotifPipelineEnabled()) {
-            mNewNotifPipeline.get().initialize(mNotificationListener, rowBinder);
+            mNewNotifPipeline.get().initialize(mNotificationListener, mNotificationRowBinder);
         }
         mEntryManager.attach(mNotificationListener);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
index 153ca22..be7f0a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
 import android.content.Context;
@@ -66,6 +65,7 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -117,7 +117,6 @@
             HeadsUpManagerPhone headsUpManagerPhone,
             DynamicPrivacyController dynamicPrivacyController,
             BypassHeadsUpNotifier bypassHeadsUpNotifier,
-            @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
             Lazy<NewNotifPipeline> newNotifPipeline,
             FalsingManager falsingManager,
             BroadcastDispatcher broadcastDispatcher,
@@ -184,6 +183,7 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
+            NotificationRowBinderImpl notificationRowBinder,
             DismissCallbackRegistry dismissCallbackRegistry) {
         return new StatusBar(
                 context,
@@ -199,7 +199,6 @@
                 headsUpManagerPhone,
                 dynamicPrivacyController,
                 bypassHeadsUpNotifier,
-                allowNotificationLongPress,
                 newNotifPipeline,
                 falsingManager,
                 broadcastDispatcher,
@@ -265,6 +264,7 @@
                 keyguardDismissUtil,
                 extensionController,
                 userInfoControllerImpl,
+                notificationRowBinder,
                 dismissCallbackRegistry);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index ddacc3a..4f0af9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -188,7 +188,7 @@
             // NOTE: This receiver could run before this method returns, as it's not dispatching
             // on the main thread and BroadcastDispatcher may not need to register with Context.
             // The receiver will return immediately if the view does not have a Handler yet.
-            mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter,
+            mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter,
                     Dependency.get(Dependency.TIME_TICK_HANDLER), UserHandle.ALL);
             Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
                     StatusBarIconController.ICON_BLACKLIST);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
index 2e26711..b4c154a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
@@ -96,7 +96,7 @@
         filter.addAction(Intent.ACTION_TIME_CHANGED);
         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
         filter.addAction(Intent.ACTION_LOCALE_CHANGED);
-        mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter,
+        mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter,
                 Dependency.get(Dependency.TIME_TICK_HANDLER));
 
         updateClock();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 570f153..cb40d77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -79,7 +79,8 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
         filter.addAction(LocationManager.MODE_CHANGED_ACTION);
-        mBroadcastDispatcher.registerReceiver(this, filter, new Handler(bgLooper), UserHandle.ALL);
+        mBroadcastDispatcher.registerReceiverWithHandler(this, filter,
+                new Handler(bgLooper), UserHandle.ALL);
 
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mStatusBarManager
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 6b842d5..5916180 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -654,7 +654,7 @@
     }
 
     boolean isDataDisabled() {
-        return !mPhone.isDataCapable();
+        return !mPhone.isDataConnectionEnabled();
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index f640d03..679fa7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -326,7 +326,7 @@
         filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
         filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        mBroadcastDispatcher.registerReceiver(this, filter, mReceiverHandler);
+        mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
         mListening = true;
 
         updateMobileControllers();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 019ef3b..312c4ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -126,7 +126,8 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
-        broadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, bgHandler, UserHandle.ALL);
+        broadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, bgHandler,
+                UserHandle.ALL);
 
         // TODO: re-register network callback on user change.
         mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 7758aba..31b9952 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -88,7 +88,7 @@
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
-        mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() {
+        mBroadcastDispatcher.registerReceiverWithHandler(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
                 if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
index 7cdba86..cc6d607 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
@@ -83,17 +83,19 @@
      * Provide a Background-Thread Executor by default.
      */
     @Provides
+    @Singleton
     public static Executor provideExecutor(@Background Looper looper) {
-        return new ExecutorImpl(new Handler(looper));
+        return new ExecutorImpl(looper);
     }
 
     /**
      * Provide a Background-Thread Executor.
      */
     @Provides
+    @Singleton
     @Background
     public static Executor provideBackgroundExecutor(@Background Looper looper) {
-        return new ExecutorImpl(new Handler(looper));
+        return new ExecutorImpl(looper);
     }
 
     /**
@@ -109,26 +111,29 @@
      * Provide a Background-Thread Executor by default.
      */
     @Provides
+    @Singleton
     public static DelayableExecutor provideDelayableExecutor(@Background Looper looper) {
-        return new ExecutorImpl(new Handler(looper));
+        return new ExecutorImpl(looper);
     }
 
     /**
      * Provide a Background-Thread Executor.
      */
     @Provides
+    @Singleton
     @Background
     public static DelayableExecutor provideBackgroundDelayableExecutor(@Background Looper looper) {
-        return new ExecutorImpl(new Handler(looper));
+        return new ExecutorImpl(looper);
     }
 
     /**
      * Provide a Main-Thread Executor.
      */
     @Provides
+    @Singleton
     @Main
     public static DelayableExecutor provideMainDelayableExecutor(@Main Looper looper) {
-        return new ExecutorImpl(new Handler(looper));
+        return new ExecutorImpl(looper);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java
index 7e77321..2bbf950 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java
@@ -17,37 +17,69 @@
 package com.android.systemui.util.concurrency;
 
 import android.os.Handler;
-import android.os.HandlerExecutor;
+import android.os.Looper;
 import android.os.Message;
 
+import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.TimeUnit;
 
 /**
  * Implementations of {@link DelayableExecutor} for SystemUI.
  */
-public class ExecutorImpl extends HandlerExecutor implements DelayableExecutor {
+public class ExecutorImpl implements DelayableExecutor {
     private final Handler mHandler;
 
-    public ExecutorImpl(Handler handler) {
-        super(handler);
-        mHandler = handler;
+    ExecutorImpl(Looper looper) {
+        mHandler = new Handler(looper, this::onHandleMessage);
+    }
+
+    @Override
+    public void execute(Runnable command) {
+        if (!mHandler.post(command)) {
+            throw new RejectedExecutionException(mHandler + " is shutting down");
+        }
     }
 
     @Override
     public Runnable executeDelayed(Runnable r, long delay, TimeUnit unit) {
-        Object token = new Object();
-        Message m = mHandler.obtainMessage(0, token);
+        ExecutionToken token = new ExecutionToken(r);
+        Message m = mHandler.obtainMessage(MSG_EXECUTE_RUNNABLE, token);
         mHandler.sendMessageDelayed(m, unit.toMillis(delay));
 
-        return () -> mHandler.removeCallbacksAndMessages(token);
+        return token;
     }
 
     @Override
     public Runnable executeAtTime(Runnable r, long uptimeMillis, TimeUnit unit) {
-        Object token = new Object();
-        Message m = mHandler.obtainMessage(0, token);
+        ExecutionToken token = new ExecutionToken(r);
+        Message m = mHandler.obtainMessage(MSG_EXECUTE_RUNNABLE, token);
         mHandler.sendMessageAtTime(m, unit.toMillis(uptimeMillis));
 
-        return () -> mHandler.removeCallbacksAndMessages(token);
+        return token;
     }
+
+    private boolean onHandleMessage(Message msg) {
+        if (msg.what == MSG_EXECUTE_RUNNABLE) {
+            ExecutionToken token = (ExecutionToken) msg.obj;
+            token.runnable.run();
+        } else {
+            throw new IllegalStateException("Unrecognized message: " + msg.what);
+        }
+        return true;
+    }
+
+    private class ExecutionToken implements Runnable {
+        public final Runnable runnable;
+
+        private ExecutionToken(Runnable runnable) {
+            this.runnable = runnable;
+        }
+
+        @Override
+        public void run() {
+            mHandler.removeCallbacksAndMessages(this);
+        }
+    }
+
+    private static final int MSG_EXECUTE_RUNNABLE = 0;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index a4ed31d..112ae6f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -1008,7 +1008,7 @@
             filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
             filter.addAction(Intent.ACTION_SCREEN_OFF);
             filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
-            mBroadcastDispatcher.registerReceiver(this, filter, mWorker);
+            mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mWorker);
         }
 
         public void destroy() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 0c22aae..2e0fb3b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -150,10 +150,10 @@
 
     @Test
     public void testReceiversRegistered() {
-        verify(mBroadcastDispatcher, atLeastOnce()).registerReceiver(
+        verify(mBroadcastDispatcher, atLeastOnce()).registerReceiverWithHandler(
                 eq(mKeyguardUpdateMonitor.mBroadcastReceiver),
                 any(IntentFilter.class), any(Handler.class));
-        verify(mBroadcastDispatcher, atLeastOnce()).registerReceiver(
+        verify(mBroadcastDispatcher, atLeastOnce()).registerReceiverWithHandler(
                 eq(mKeyguardUpdateMonitor.mBroadcastAllReceiver),
                 any(IntentFilter.class), any(Handler.class), eq(UserHandle.ALL));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
index 0c53b03..2ecc8ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
@@ -52,6 +52,7 @@
     @Override
     protected <T> T createDependency(Object key) {
         if (mObjs.containsKey(key)) return (T) mObjs.get(key);
+
         mInstantiatedObjects.add(key);
         return super.createDependency(key);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index 42fbf59..22b1837 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -27,6 +27,8 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
 import junit.framework.Assert.assertSame
 import org.junit.Before
 import org.junit.Test
@@ -39,6 +41,7 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
 
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
@@ -73,6 +76,8 @@
     @Mock
     private lateinit var mockHandler: Handler
 
+    private lateinit var executor: Executor
+
     @Captor
     private lateinit var argumentCaptor: ArgumentCaptor<ReceiverData>
 
@@ -83,6 +88,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
+        executor = FakeExecutor(FakeSystemClock())
 
         broadcastDispatcher = TestBroadcastDispatcher(
                 mockContext,
@@ -98,8 +104,9 @@
 
     @Test
     fun testAddingReceiverToCorrectUBR() {
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
-        broadcastDispatcher.registerReceiver(
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user0)
+        broadcastDispatcher.registerReceiverWithHandler(
                 broadcastReceiverOther, intentFilterOther, mockHandler, user1)
 
         testableLooper.processAllMessages()
@@ -115,9 +122,29 @@
     }
 
     @Test
+    fun testAddingReceiverToCorrectUBR_executor() {
+        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, executor, user0)
+        broadcastDispatcher.registerReceiver(
+                broadcastReceiverOther, intentFilterOther, executor, user1)
+
+        testableLooper.processAllMessages()
+
+        verify(mockUBRUser0).registerReceiver(capture(argumentCaptor))
+
+        assertSame(broadcastReceiver, argumentCaptor.value.receiver)
+        assertSame(intentFilter, argumentCaptor.value.filter)
+
+        verify(mockUBRUser1).registerReceiver(capture(argumentCaptor))
+        assertSame(broadcastReceiverOther, argumentCaptor.value.receiver)
+        assertSame(intentFilterOther, argumentCaptor.value.filter)
+    }
+
+    @Test
     fun testRemovingReceiversRemovesFromAllUBR() {
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user1)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user0)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user1)
 
         broadcastDispatcher.unregisterReceiver(broadcastReceiver)
 
@@ -129,8 +156,10 @@
 
     @Test
     fun testRemoveReceiverFromUser() {
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user1)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user0)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, user1)
 
         broadcastDispatcher.unregisterReceiverForUser(broadcastReceiver, user0)
 
@@ -143,8 +172,8 @@
     @Test
     fun testRegisterCurrentAsActualUser() {
         setUserMock(mockContext, user1)
-        broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler,
-                UserHandle.CURRENT)
+        broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+                mockHandler, UserHandle.CURRENT)
 
         testableLooper.processAllMessages()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
index 21ed155..7821ae2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
@@ -26,6 +26,8 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
@@ -69,8 +71,6 @@
     @Mock
     private lateinit var mockContext: Context
     @Mock
-    private lateinit var mockHandler: Handler
-    @Mock
     private lateinit var mPendingResult: BroadcastReceiver.PendingResult
 
     @Captor
@@ -81,12 +81,14 @@
     private lateinit var intentFilter: IntentFilter
     private lateinit var intentFilterOther: IntentFilter
     private lateinit var handler: Handler
+    private lateinit var fakeExecutor: FakeExecutor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         handler = Handler(testableLooper.looper)
+        fakeExecutor = FakeExecutor(FakeSystemClock())
 
         userBroadcastDispatcher = UserBroadcastDispatcher(
                 mockContext, USER_ID, handler, testableLooper.looper)
@@ -108,7 +110,7 @@
         intentFilter = IntentFilter(ACTION_1)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         testableLooper.processAllMessages()
 
         assertTrue(userBroadcastDispatcher.isRegistered())
@@ -128,7 +130,7 @@
         intentFilter = IntentFilter(ACTION_1)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         testableLooper.processAllMessages()
         reset(mockContext)
 
@@ -151,9 +153,9 @@
         }
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, mockHandler, USER_HANDLE))
+                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         testableLooper.processAllMessages()
         assertTrue(userBroadcastDispatcher.isRegistered())
@@ -179,14 +181,15 @@
         intentFilterOther = IntentFilter(ACTION_2)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         val intent = Intent(ACTION_2)
 
         userBroadcastDispatcher.onReceive(mockContext, intent)
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         verify(broadcastReceiver, never()).onReceive(any(), any())
         verify(broadcastReceiverOther).onReceive(mockContext, intent)
@@ -198,14 +201,15 @@
         intentFilterOther = IntentFilter(ACTION_2)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilterOther, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         val intent = Intent(ACTION_2)
 
         userBroadcastDispatcher.onReceive(mockContext, intent)
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         verify(broadcastReceiver).onReceive(mockContext, intent)
     }
@@ -218,14 +222,15 @@
         intentFilterOther.addCategory(CATEGORY_2)
 
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         val intent = Intent(ACTION_1)
 
         userBroadcastDispatcher.onReceive(mockContext, intent)
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         verify(broadcastReceiver).onReceive(mockContext, intent)
         verify(broadcastReceiverOther).onReceive(mockContext, intent)
@@ -235,12 +240,13 @@
     fun testPendingResult() {
         intentFilter = IntentFilter(ACTION_1)
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
 
         val intent = Intent(ACTION_1)
         userBroadcastDispatcher.onReceive(mockContext, intent)
 
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         verify(broadcastReceiver).onReceive(mockContext, intent)
         verify(broadcastReceiver).pendingResult = mPendingResult
@@ -250,15 +256,16 @@
     fun testRemoveReceiverReferences() {
         intentFilter = IntentFilter(ACTION_1)
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
 
         intentFilterOther = IntentFilter(ACTION_1)
         intentFilterOther.addAction(ACTION_2)
         userBroadcastDispatcher.registerReceiver(
-                ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE))
+                ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
 
         userBroadcastDispatcher.unregisterReceiver(broadcastReceiver)
         testableLooper.processAllMessages()
+        fakeExecutor.runAllReady()
 
         assertFalse(userBroadcastDispatcher.isReceiverReferenceHeld(broadcastReceiver))
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 167f361..548da8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -110,7 +110,7 @@
     @Test
     public void testReceiverIsRegisteredToDispatcherOnStart() {
         mPowerUI.start();
-        verify(mBroadcastDispatcher).registerReceiver(
+        verify(mBroadcastDispatcher).registerReceiverWithHandler(
                 any(BroadcastReceiver.class),
                 any(IntentFilter.class),
                 any(Handler.class)); //PowerUI does not call with User
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 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 c559265..1e0179d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -58,16 +58,13 @@
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dependency;
-import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
-import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -81,21 +78,24 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache;
+import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.leak.LeakDetector;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -129,22 +129,16 @@
     @Mock private HeadsUpManager mHeadsUpManager;
     @Mock private RankingMap mRankingMap;
     @Mock private RemoteInputController mRemoteInputController;
-
-    // Dependency mocks:
-    @Mock private ForegroundServiceController mForegroundServiceController;
+    @Mock private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
     @Mock private NotificationLockscreenUserManager mLockscreenUserManager;
     @Mock private NotificationGroupManager mGroupManager;
     @Mock private NotificationGutsManager mGutsManager;
     @Mock private NotificationRemoteInputManager mRemoteInputManager;
-    @Mock private NotificationListener mNotificationListener;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
-    @Mock private VisualStabilityManager mVisualStabilityManager;
-    @Mock private MetricsLogger mMetricsLogger;
-    @Mock private SmartReplyController mSmartReplyController;
     @Mock private RowInflaterTask mAsyncInflationTask;
-    @Mock private NotificationRowBinder mMockedRowBinder;
     @Mock private NotifLog mNotifLog;
     @Mock private FeatureFlags mFeatureFlags;
+    @Mock private LeakDetector mLeakDetector;
 
     private int mId;
     private NotificationEntry mEntry;
@@ -191,21 +185,9 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mDependency.injectMockDependency(ShadeController.class);
-        mDependency.injectTestDependency(ForegroundServiceController.class,
-                mForegroundServiceController);
-        mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
-                mLockscreenUserManager);
-        mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
-        mDependency.injectTestDependency(NotificationGutsManager.class, mGutsManager);
-        mDependency.injectTestDependency(NotificationRemoteInputManager.class, mRemoteInputManager);
-        mDependency.injectTestDependency(NotificationListener.class, mNotificationListener);
-        mDependency.injectTestDependency(DeviceProvisionedController.class,
-                mDeviceProvisionedController);
-        mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
-        mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
-        mDependency.injectTestDependency(SmartReplyController.class, mSmartReplyController);
-        mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
+        if (!mDependency.hasInstantiatedDependency(SmartReplyController.class)) {
+            mDependency.injectMockDependency(SmartReplyController.class);
+        }
         mDependency.injectMockDependency(NotificationMediaManager.class);
 
         mCountDownLatch = new CountDownLatch(1);
@@ -221,6 +203,23 @@
 
         mEntry.expandedIcon = mock(StatusBarIconView.class);
 
+        NotificationRowContentBinder contentBinder = new NotificationContentInflater(
+                mock(NotifRemoteViewCache.class),
+                mRemoteInputManager);
+
+        NotificationRowBinderImpl notificationRowBinder =
+                new NotificationRowBinderImpl(mContext,
+                        mRemoteInputManager,
+                        mLockscreenUserManager,
+                        contentBinder,
+                        true, /* allowLongPress */
+                        mock(KeyguardBypassController.class),
+                        mock(StatusBarStateController.class),
+                        mGroupManager,
+                        mGutsManager,
+                        mNotificationInterruptionStateProvider,
+                        mock(NotificationLogger.class));
+
         when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false);
         when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
         mEntryManager = new TestableNotificationEntryManager(
@@ -233,24 +232,22 @@
                         mock(NotificationFilter.class),
                         mNotifLog,
                         mock(NotificationSectionsFeatureManager.class),
-                        mock(PeopleNotificationIdentifier.class)),
+                        mock(PeopleNotificationIdentifier.class),
+                        mock(HighPriorityProvider.class)),
                 mEnvironment,
-                mFeatureFlags
+                mFeatureFlags,
+                () -> notificationRowBinder,
+                () -> mRemoteInputManager,
+                mLeakDetector
         );
         mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mHeadsUpManager);
         mEntryManager.addNotificationEntryListener(mEntryListener);
         mEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
 
-        NotificationRowBinderImpl notificationRowBinder =
-                new NotificationRowBinderImpl(mContext, true, /* allowLongPress */
-                        mock(KeyguardBypassController.class),
-                        mock(StatusBarStateController.class),
-                        mock(NotificationLogger.class));
         notificationRowBinder.setUpWithPresenter(
                 mPresenter, mListContainer, mHeadsUpManager, mBindCallback);
         notificationRowBinder.setInflationCallback(mEntryManager);
         notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class));
-        mEntryManager.setRowBinder(notificationRowBinder);
 
         setUserSentiment(
                 mEntry.getKey(), Ranking.USER_SENTIMENT_NEUTRAL);
@@ -370,9 +367,6 @@
 
     @Test
     public void testRemoveNotification_whilePending() {
-
-        mEntryManager.setRowBinder(mMockedRowBinder);
-
         mEntryManager.addNotification(mSbn, mRankingMap);
         mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
 
@@ -445,7 +439,6 @@
     @Test
     public void testLifetimeExtenders_ifNotificationIsRetainedItIsntRemoved() {
         // GIVEN an entry manager with a notification
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
 
         // GIVEN a lifetime extender that always tries to extend lifetime
@@ -469,7 +462,6 @@
         Assert.sMainLooper = TestableLooper.get(this).getLooper();
 
         // GIVEN an entry manager with a notification whose life has been extended
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
         final FakeNotificationLifetimeExtender extender = new FakeNotificationLifetimeExtender();
         mEntryManager.addNotificationLifetimeExtender(extender);
@@ -488,7 +480,6 @@
     @Test
     public void testLifetimeExtenders_whenNotificationUpdatedRetainersAreCanceled() {
         // GIVEN an entry manager with a notification whose life has been extended
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
         NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class);
         when(extender.shouldExtendLifetime(mEntry)).thenReturn(true);
@@ -505,7 +496,6 @@
     @Test
     public void testLifetimeExtenders_whenNewExtenderTakesPrecedenceOldExtenderIsCanceled() {
         // GIVEN an entry manager with a notification
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
 
         // GIVEN two lifetime extenders, the first which never extends and the second which
@@ -544,7 +534,6 @@
     @Test
     public void testRemoveInterceptor_interceptsDontGetRemoved() throws InterruptedException {
         // GIVEN an entry manager with a notification
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
 
         // GIVEN interceptor that intercepts that entry
@@ -566,7 +555,6 @@
         Assert.sMainLooper = TestableLooper.get(this).getLooper();
 
         // GIVEN an entry manager with a notification
-        mEntryManager.setRowBinder(mMockedRowBinder);
         mEntryManager.addActiveNotificationForTest(mEntry);
 
         // GIVEN interceptor that doesn't intercept
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
index 1afee12..bf84f2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
@@ -18,12 +18,15 @@
 
 import com.android.systemui.statusbar.FeatureFlags
 import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinder
 import com.android.systemui.statusbar.notification.logging.NotifLog
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
 import com.android.systemui.statusbar.phone.NotificationGroupManager
+import com.android.systemui.util.leak.LeakDetector
 
 import java.util.concurrent.CountDownLatch
 
@@ -35,8 +38,12 @@
     gm: NotificationGroupManager,
     rm: NotificationRankingManager,
     ke: KeyguardEnvironment,
-    ff: FeatureFlags
-) : NotificationEntryManager(log, gm, rm, ke, ff) {
+    ff: FeatureFlags,
+    rb: dagger.Lazy<NotificationRowBinder>,
+    notificationRemoteInputManagerLazy: dagger.Lazy<NotificationRemoteInputManager>,
+    leakDetector: LeakDetector
+) : NotificationEntryManager(log, gm, rm, ke, ff, rb,
+        notificationRemoteInputManagerLazy, leakDetector) {
 
     public var countDownLatch: CountDownLatch = CountDownLatch(1)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java
deleted file mode 100644
index a06d6c1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection;
-
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.RankingBuilder;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class GroupEntryTest extends SysuiTestCase {
-    @Test
-    public void testIsHighPriority_addChild() {
-        // GIVEN a GroupEntry with a lowPrioritySummary and no children
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        final NotificationEntry lowPrioritySummary = createNotifEntry(false);
-        setSummary(parentEntry, lowPrioritySummary);
-        assertFalse(parentEntry.isHighPriority());
-
-        // WHEN we add a high priority child and invalidate derived members
-        addChild(parentEntry, createNotifEntry(true));
-        parentEntry.invalidateDerivedMembers();
-
-        // THEN the GroupEntry's priority is updated to high even though the summary is still low
-        // priority
-        assertTrue(parentEntry.isHighPriority());
-        assertFalse(lowPrioritySummary.isHighPriority());
-    }
-
-    @Test
-    public void testIsHighPriority_clearChildren() {
-        // GIVEN a GroupEntry with a lowPrioritySummary and high priority children
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        setSummary(parentEntry, createNotifEntry(false));
-        addChild(parentEntry, createNotifEntry(true));
-        addChild(parentEntry, createNotifEntry(true));
-        addChild(parentEntry, createNotifEntry(true));
-        assertTrue(parentEntry.isHighPriority());
-
-        // WHEN we clear the children and invalidate derived members
-        parentEntry.clearChildren();
-        parentEntry.invalidateDerivedMembers();
-
-        // THEN the parentEntry isn't high priority anymore
-        assertFalse(parentEntry.isHighPriority());
-    }
-
-    @Test
-    public void testIsHighPriority_summaryUpdated() {
-        // GIVEN a GroupEntry with a lowPrioritySummary and no children
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        final NotificationEntry lowPrioritySummary = createNotifEntry(false);
-        setSummary(parentEntry, lowPrioritySummary);
-        assertFalse(parentEntry.isHighPriority());
-
-        // WHEN the summary changes to high priority and invalidates its derived members
-        lowPrioritySummary.setRanking(
-                new RankingBuilder()
-                        .setKey(lowPrioritySummary.getKey())
-                        .setImportance(IMPORTANCE_HIGH)
-                        .build());
-        lowPrioritySummary.invalidateDerivedMembers();
-        assertTrue(lowPrioritySummary.isHighPriority());
-
-        // THEN the GroupEntry's priority is updated to high
-        assertTrue(parentEntry.isHighPriority());
-    }
-
-    @Test
-    public void testIsHighPriority_checkChildrenToCalculatePriority() {
-        // GIVEN:
-        // GroupEntry = parentEntry, summary = lowPrioritySummary
-        //      NotificationEntry = lowPriorityChild
-        //      NotificationEntry = highPriorityChild
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        setSummary(parentEntry, createNotifEntry(false));
-        addChild(parentEntry, createNotifEntry(false));
-        addChild(parentEntry, createNotifEntry(true));
-
-        // THEN the GroupEntry parentEntry is high priority since it has a high priority child
-        assertTrue(parentEntry.isHighPriority());
-    }
-
-    @Test
-    public void testIsHighPriority_childEntryRankingUpdated() {
-        // GIVEN:
-        // GroupEntry = parentEntry, summary = lowPrioritySummary
-        //      NotificationEntry = lowPriorityChild
-        final GroupEntry parentEntry = new GroupEntry("test_group_key");
-        final NotificationEntry lowPriorityChild = createNotifEntry(false);
-        setSummary(parentEntry, createNotifEntry(false));
-        addChild(parentEntry, lowPriorityChild);
-
-        // WHEN the child entry ranking changes to high priority and invalidates its derived members
-        lowPriorityChild.setRanking(
-                new RankingBuilder()
-                        .setKey(lowPriorityChild.getKey())
-                        .setImportance(IMPORTANCE_HIGH)
-                        .build());
-        lowPriorityChild.invalidateDerivedMembers();
-
-        // THEN the parent entry's high priority value is updated - but not the parent's summary
-        assertTrue(parentEntry.isHighPriority());
-        assertFalse(parentEntry.getSummary().isHighPriority());
-    }
-
-    private NotificationEntry createNotifEntry(boolean highPriority) {
-        return new NotificationEntryBuilder()
-                .setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN)
-                .build();
-    }
-
-    private void setSummary(GroupEntry parent, NotificationEntry summary) {
-        parent.setSummary(summary);
-        summary.setParent(parent);
-    }
-
-    private void addChild(GroupEntry parent, NotificationEntry child) {
-        parent.addChild(child);
-        child.setParent(parent);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
new file mode 100644
index 0000000..93909dc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class HighPriorityProviderTest extends SysuiTestCase {
+    @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+    private HighPriorityProvider mHighPriorityProvider;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mHighPriorityProvider = new HighPriorityProvider(mPeopleNotificationIdentifier);
+    }
+
+    @Test
+    public void highImportance() {
+        // GIVEN notification has high importance
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setImportance(IMPORTANCE_HIGH)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false);
+
+        // THEN it has high priority
+        assertTrue(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void peopleNotification() {
+        // GIVEN notification is low importance and is a people notification
+        final Notification notification = new Notification.Builder(mContext, "test")
+                .build();
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setImportance(IMPORTANCE_LOW)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(true);
+
+        // THEN it has high priority
+        assertTrue(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void messagingStyle() {
+        // GIVEN notification is low importance but has messaging style
+        final Notification notification = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle(""))
+                .build();
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false);
+
+        // THEN it has high priority
+        assertTrue(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void lowImportanceForeground() {
+        // GIVEN notification is low importance and is associated with a foreground service
+        final Notification notification = mock(Notification.class);
+        when(notification.isForegroundService()).thenReturn(true);
+
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setImportance(IMPORTANCE_LOW)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false);
+
+        // THEN it has high priority
+        assertTrue(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void minImportanceForeground() {
+        // GIVEN notification is low importance and is associated with a foreground service
+        final Notification notification = mock(Notification.class);
+        when(notification.isForegroundService()).thenReturn(true);
+
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setImportance(IMPORTANCE_MIN)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(false);
+
+        // THEN it does NOT have high priority
+        assertFalse(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void userChangeTrumpsHighPriorityCharacteristics() {
+        // GIVEN notification has high priority characteristics but the user changed the importance
+        // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN)
+        final Notification notification = new Notification.Builder(mContext, "test")
+                .setStyle(new Notification.MessagingStyle(""))
+                .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                .build();
+        final NotificationChannel channel = new NotificationChannel("a", "a",
+                IMPORTANCE_LOW);
+        channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+
+        final NotificationEntry entry = new NotificationEntryBuilder()
+                .setNotification(notification)
+                .setChannel(channel)
+                .build();
+        when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn())).thenReturn(true);
+
+        // THEN it does NOT have high priority
+        assertFalse(mHighPriorityProvider.isHighPriority(entry));
+    }
+
+    @Test
+    public void testIsHighPriority_summaryUpdated() {
+        // GIVEN a GroupEntry with a lowPrioritySummary and no children
+        final GroupEntry parentEntry = new GroupEntry("test_group_key");
+        final NotificationEntry lowPrioritySummary = createNotifEntry(false);
+        setSummary(parentEntry, lowPrioritySummary);
+        assertFalse(mHighPriorityProvider.isHighPriority(parentEntry));
+
+        // WHEN the summary changes to high priority
+        lowPrioritySummary.setRanking(
+                new RankingBuilder()
+                        .setKey(lowPrioritySummary.getKey())
+                        .setImportance(IMPORTANCE_HIGH)
+                        .build());
+        assertTrue(mHighPriorityProvider.isHighPriority(lowPrioritySummary));
+
+        // THEN the GroupEntry's priority is updated to high
+        assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
+    }
+
+    @Test
+    public void testIsHighPriority_checkChildrenToCalculatePriority() {
+        // GIVEN:
+        // GroupEntry = parentEntry, summary = lowPrioritySummary
+        //      NotificationEntry = lowPriorityChild
+        //      NotificationEntry = highPriorityChild
+        final GroupEntry parentEntry = new GroupEntry("test_group_key");
+        setSummary(parentEntry, createNotifEntry(false));
+        addChild(parentEntry, createNotifEntry(false));
+        addChild(parentEntry, createNotifEntry(true));
+
+        // THEN the GroupEntry parentEntry is high priority since it has a high priority child
+        assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
+    }
+
+    @Test
+    public void testIsHighPriority_childEntryRankingUpdated() {
+        // GIVEN:
+        // GroupEntry = parentEntry, summary = lowPrioritySummary
+        //      NotificationEntry = lowPriorityChild
+        final GroupEntry parentEntry = new GroupEntry("test_group_key");
+        final NotificationEntry lowPriorityChild = createNotifEntry(false);
+        setSummary(parentEntry, createNotifEntry(false));
+        addChild(parentEntry, lowPriorityChild);
+
+        // WHEN the child entry ranking changes to high priority
+        lowPriorityChild.setRanking(
+                new RankingBuilder()
+                        .setKey(lowPriorityChild.getKey())
+                        .setImportance(IMPORTANCE_HIGH)
+                        .build());
+
+        // THEN the parent entry's high priority value is updated - but not the parent's summary
+        assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
+        assertFalse(mHighPriorityProvider.isHighPriority(parentEntry.getSummary()));
+    }
+
+    private NotificationEntry createNotifEntry(boolean highPriority) {
+        return new NotificationEntryBuilder()
+                .setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN)
+                .build();
+    }
+
+    private void setSummary(GroupEntry parent, NotificationEntry summary) {
+        parent.setSummary(summary);
+        summary.setParent(parent);
+    }
+
+    private void addChild(GroupEntry parent, NotificationEntry child) {
+        parent.addChild(child);
+        child.setParent(parent);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 39ae68a..5b0b668 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -21,8 +21,6 @@
 import static android.app.Notification.CATEGORY_EVENT;
 import static android.app.Notification.CATEGORY_MESSAGE;
 import static android.app.Notification.CATEGORY_REMINDER;
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
 
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
@@ -92,44 +90,6 @@
     }
 
     @Test
-    public void testIsHighPriority_notificationUpdates() {
-        // GIVEN a notification with high importance
-        final NotificationEntry entryHigh = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-
-        // WHEN we get the value for the high priority entry, we're caching the high priority value
-        assertTrue(entryHigh.isHighPriority());
-
-        // WHEN we change the ranking and derived members (high priority) are invalidated
-        entryHigh.setRanking(
-                new RankingBuilder()
-                        .setKey(entryHigh.getKey())
-                        .setImportance(IMPORTANCE_MIN)
-                        .build());
-        entryHigh.invalidateDerivedMembers();
-
-        // THEN the priority is recalculated and is now low
-        assertFalse(entryHigh.isHighPriority());
-
-        // WHEN the sbn is updated to have messaging style (high priority characteristic)
-        //  AND the entry invalidates its derived members
-        final Notification notification =
-                new Notification.Builder(mContext, "test")
-                        .setStyle(new Notification.MessagingStyle(""))
-                        .build();
-        final StatusBarNotification sbn = entryHigh.getSbn();
-        entryHigh.setSbn(new StatusBarNotification(
-                sbn.getPackageName(), sbn.getPackageName(),
-                sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
-                notification, sbn.getUser(), sbn.getOverrideGroupKey(), 0));
-        entryHigh.invalidateDerivedMembers();
-
-        // THEN the priority is recalculated and is now high
-        assertTrue(entryHigh.isHighPriority());
-    }
-
-    @Test
     public void testIsExemptFromDndVisualSuppression_foreground() {
         mEntry.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
index 10450fa..e273191 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.notification.NotificationFilter
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
 import com.android.systemui.statusbar.notification.logging.NotifLog
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
@@ -62,7 +63,8 @@
                 mock(NotificationFilter::class.java),
                 mock(NotifLog::class.java),
                 mock(NotificationSectionsFeatureManager::class.java),
-                personNotificationIdentifier
+                personNotificationIdentifier,
+                HighPriorityProvider(personNotificationIdentifier)
         )
     }
 
@@ -182,7 +184,8 @@
         filter: NotificationFilter,
         notifLog: NotifLog,
         sectionsFeatureManager: NotificationSectionsFeatureManager,
-        peopleNotificationIdentifier: PeopleNotificationIdentifier
+        peopleNotificationIdentifier: PeopleNotificationIdentifier,
+        highPriorityProvider: HighPriorityProvider
     ) : NotificationRankingManager(
         mediaManager,
         groupManager,
@@ -190,7 +193,8 @@
         filter,
         notifLog,
         sectionsFeatureManager,
-        peopleNotificationIdentifier
+        peopleNotificationIdentifier,
+        highPriorityProvider
     ) {
         fun applyTestRankingMap(r: RankingMap) {
             rankingMap = r
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
index 979b8a9..f921cf9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
@@ -66,6 +67,7 @@
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock private HighPriorityProvider mHighPriorityProvider;
     @Mock private NotifListBuilderImpl mNotifListBuilder;
 
     private NotificationEntry mEntry;
@@ -78,7 +80,7 @@
         mKeyguardCoordinator = new KeyguardCoordinator(
                 mContext, mMainHandler, mKeyguardStateController, mLockscreenUserManager,
                 mBroadcastDispatcher, mStatusBarStateController,
-                mKeyguardUpdateMonitor);
+                mKeyguardUpdateMonitor, mHighPriorityProvider);
 
         mEntry = new NotificationEntryBuilder()
                 .setUser(new UserHandle(NOTIF_USER_ID))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java
deleted file mode 100644
index 6fa1a89..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.provider;
-
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_LOW;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.Person;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class IsHighPriorityProviderTest extends SysuiTestCase {
-    private IsHighPriorityProvider mIsHighPriorityProvider;
-
-    @Before
-    public void setup() {
-        mIsHighPriorityProvider = new IsHighPriorityProvider();
-    }
-
-    @Test
-    public void testCache() {
-        // GIVEN a notification with high importance
-        final NotificationEntry entryHigh = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-
-        // GIVEN notification with min importance
-        final NotificationEntry entryMin = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_MIN)
-                .build();
-
-        // WHEN we get the value for the high priority entry
-        assertTrue(mIsHighPriorityProvider.get(entryHigh));
-
-        // THEN the value is cached, so even when passed an entryMin, we still get high priority
-        assertTrue(mIsHighPriorityProvider.get(entryMin));
-
-        // UNTIL the provider is invalidated
-        mIsHighPriorityProvider.invalidate();
-
-        // THEN the priority is recalculated
-        assertFalse(mIsHighPriorityProvider.get(entryMin));
-    }
-
-    @Test
-    public void highImportance() {
-        // GIVEN notification has high importance
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setImportance(IMPORTANCE_HIGH)
-                .build();
-
-        // THEN it has high priority
-        assertTrue(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void peopleNotification() {
-        // GIVEN notification is low importance but has a person associated with it
-        final Notification notification = new Notification.Builder(mContext, "test")
-                .addPerson(
-                        new Person.Builder()
-                                .setName("name")
-                                .setKey("abc")
-                                .setUri("uri")
-                                .setBot(true)
-                                .build())
-                .build();
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .setImportance(IMPORTANCE_LOW)
-                .build();
-
-        // THEN it has high priority
-        assertTrue(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void messagingStyle() {
-        // GIVEN notification is low importance but has messaging style
-        final Notification notification = new Notification.Builder(mContext, "test")
-                .setStyle(new Notification.MessagingStyle(""))
-                .build();
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .build();
-
-        // THEN it has high priority
-        assertTrue(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void lowImportanceForeground() {
-        // GIVEN notification is low importance and is associated with a foreground service
-        final Notification notification = mock(Notification.class);
-        when(notification.isForegroundService()).thenReturn(true);
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .setImportance(IMPORTANCE_LOW)
-                .build();
-
-        // THEN it has high priority
-        assertTrue(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void minImportanceForeground() {
-        // GIVEN notification is low importance and is associated with a foreground service
-        final Notification notification = mock(Notification.class);
-        when(notification.isForegroundService()).thenReturn(true);
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .setImportance(IMPORTANCE_MIN)
-                .build();
-
-        // THEN it does NOT have high priority
-        assertFalse(mIsHighPriorityProvider.get(entry));
-    }
-
-    @Test
-    public void userChangeTrumpsHighPriorityCharacteristics() {
-        // GIVEN notification has high priority characteristics but the user changed the importance
-        // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN)
-        final Notification notification = new Notification.Builder(mContext, "test")
-                .addPerson(
-                        new Person.Builder()
-                                .setName("name")
-                                .setKey("abc")
-                                .setUri("uri")
-                                .setBot(true)
-                                .build())
-                .setStyle(new Notification.MessagingStyle(""))
-                .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
-                .build();
-
-        final NotificationChannel channel = new NotificationChannel("a", "a",
-                IMPORTANCE_LOW);
-        channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
-
-        final NotificationEntry entry = new NotificationEntryBuilder()
-                .setNotification(notification)
-                .setChannel(channel)
-                .build();
-
-        // THEN it does NOT have high priority
-        assertFalse(mIsHighPriorityProvider.get(entry));
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
new file mode 100644
index 0000000..d7214f3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.widget.RemoteViews;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class NotifRemoteViewCacheImplTest extends SysuiTestCase {
+
+    private NotifRemoteViewCacheImpl mNotifRemoteViewCache;
+    private NotificationEntry mEntry;
+    private NotificationEntryListener mEntryListener;
+    @Mock private RemoteViews mRemoteViews;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mEntry = new NotificationEntryBuilder().build();
+
+        NotificationEntryManager entryManager = mock(NotificationEntryManager.class);
+        mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(entryManager);
+        ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
+                ArgumentCaptor.forClass(NotificationEntryListener.class);
+        verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture());
+        mEntryListener = entryListenerCaptor.getValue();
+    }
+
+    @Test
+    public void testPutCachedView() {
+        // GIVEN an initialized cache for an entry.
+        mEntryListener.onPendingEntryAdded(mEntry);
+
+        // WHEN a notification's cached remote views is put in.
+        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+
+        // THEN the remote view is cached.
+        assertTrue(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+        assertEquals(
+                "Cached remote view is not the one we put in.",
+                mRemoteViews,
+                mNotifRemoteViewCache.getCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+    }
+
+    @Test
+    public void testRemoveCachedView() {
+        // GIVEN a cache with a cached view.
+        mEntryListener.onPendingEntryAdded(mEntry);
+        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+
+        // WHEN we remove the cached view.
+        mNotifRemoteViewCache.removeCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED);
+
+        // THEN the remote view is not cached.
+        assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+    }
+
+    @Test
+    public void testClearCache() {
+        // GIVEN a non-empty cache.
+        mEntryListener.onPendingEntryAdded(mEntry);
+        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+        mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED, mRemoteViews);
+
+        // WHEN we clear the cache.
+        mNotifRemoteViewCache.clearCache(mEntry);
+
+        // THEN the cache is empty.
+        assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+        assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index f916fe5..cb9da6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -22,11 +22,16 @@
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.Notification;
 import android.content.Context;
@@ -35,16 +40,17 @@
 import android.os.Looper;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
-import android.util.ArrayMap;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.RemoteViews;
+import android.widget.TextView;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.filters.Suppress;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.InflationTask;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
@@ -57,6 +63,8 @@
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.HashMap;
 import java.util.concurrent.CountDownLatch;
@@ -73,8 +81,11 @@
     private Notification.Builder mBuilder;
     private ExpandableNotificationRow mRow;
 
+    @Mock private NotifRemoteViewCache mCache;
+
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
         mBuilder = new Notification.Builder(mContext).setSmallIcon(
                 R.drawable.ic_person)
                 .setContentTitle("Title")
@@ -83,7 +94,9 @@
         ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
                 mBuilder.build());
         mRow = spy(row);
-        mNotificationInflater = new NotificationContentInflater();
+        mNotificationInflater = new NotificationContentInflater(
+                mCache,
+                mock(NotificationRemoteInputManager.class));
     }
 
     @Test
@@ -174,7 +187,9 @@
                 result,
                 FLAG_CONTENT_VIEW_EXPANDED,
                 0,
-                new ArrayMap() /* cachedContentViews */, mRow,
+                mock(NotifRemoteViewCache.class),
+                mRow.getEntry(),
+                mRow,
                 true /* isNewView */, (v, p, r) -> true,
                 new InflationCallback() {
                     @Override
@@ -244,6 +259,71 @@
                 NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView));
     }
 
+    @Test
+    public void testUsesSameViewWhenCachedPossibleToReuse() throws Exception {
+        // GIVEN a cached view.
+        RemoteViews contractedRemoteView = mBuilder.createContentView();
+        when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+                .thenReturn(true);
+        when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+                .thenReturn(contractedRemoteView);
+
+        // GIVEN existing bound view with same layout id.
+        View view = contractedRemoteView.apply(mContext, null /* parent */);
+        mRow.getPrivateLayout().setContractedChild(view);
+
+        // WHEN inflater inflates
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+        // THEN the view should be re-used
+        assertEquals("Binder inflated a new view even though the old one was cached and usable.",
+                view, mRow.getPrivateLayout().getContractedChild());
+    }
+
+    @Test
+    public void testInflatesNewViewWhenCachedNotPossibleToReuse() throws Exception {
+        // GIVEN a cached remote view.
+        RemoteViews contractedRemoteView = mBuilder.createHeadsUpContentView();
+        when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+                .thenReturn(true);
+        when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+                .thenReturn(contractedRemoteView);
+
+        // GIVEN existing bound view with different layout id.
+        View view = new TextView(mContext);
+        mRow.getPrivateLayout().setContractedChild(view);
+
+        // WHEN inflater inflates
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+        // THEN the view should be a new view
+        assertNotEquals("Binder (somehow) used the same view when inflating.",
+                view, mRow.getPrivateLayout().getContractedChild());
+    }
+
+    @Test
+    public void testInflationCachesCreatedRemoteView() throws Exception {
+        // WHEN inflater inflates
+        inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+        // THEN inflater informs cache of the new remote view
+        verify(mCache).putCachedView(
+                eq(mRow.getEntry()),
+                eq(FLAG_CONTENT_VIEW_CONTRACTED),
+                any());
+    }
+
+    @Test
+    public void testUnbindRemovesCachedRemoteView() {
+        // WHEN inflated unbinds content
+        mNotificationInflater.unbindContent(mRow.getEntry(), mRow, FLAG_CONTENT_VIEW_HEADS_UP);
+
+        // THEN inflated informs cache to remove remote view
+        verify(mCache).removeCachedView(
+                eq(mRow.getEntry()),
+                eq(FLAG_CONTENT_VIEW_HEADS_UP));
+    }
+
     private static void inflateAndWait(NotificationContentInflater inflater,
             @InflationFlag int contentToInflate,
             ExpandableNotificationRow row)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index 675b3ef..84c6513 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -16,20 +16,17 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
-import android.graphics.drawable.Icon;
 import android.util.ArraySet;
 import android.view.NotificationHeaderView;
 import android.view.View;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index ccc9496..4e27770 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -70,6 +70,7 @@
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -113,6 +114,7 @@
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
     @Mock private StatusBar mStatusBar;
     @Mock private AccessibilityManager mAccessibilityManager;
+    @Mock private HighPriorityProvider mHighPriorityProvider;
 
     @Before
     public void setUp() {
@@ -128,7 +130,7 @@
         when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
 
         mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager,
-                () -> mStatusBar, mHandler, mAccessibilityManager);
+                () -> mStatusBar, mHandler, mAccessibilityManager, mHighPriorityProvider);
         mGutsManager.setUpWithPresenter(mPresenter, mStackScroller,
                 mCheckSaveListener, mOnSettingsClickListener);
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
@@ -391,6 +393,7 @@
                 .build();
 
         when(row.getIsNonblockable()).thenReturn(false);
+        when(mHighPriorityProvider.isHighPriority(entry)).thenReturn(true);
         StatusBarNotification statusBarNotification = entry.getSbn();
         mGutsManager.initializeNotificationInfo(row, notificationInfoView);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 003d803..518b670 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -267,8 +267,6 @@
                     ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class,
                             RETURNS_DEEP_STUBS);
                     when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
-                    when(notifRow.getEntry().isHighPriority())
-                            .thenReturn(children[i] == ChildType.HIPRI);
                     when(notifRow.getEntry().getBucket()).thenReturn(
                             children[i] == ChildType.HIPRI ? BUCKET_ALERTING : BUCKET_SILENT);
                     when(notifRow.getParent()).thenReturn(mNssl);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 39f037c..9bd3914 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -71,6 +71,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -88,6 +90,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.leak.LeakDetector;
 
 import org.junit.After;
 import org.junit.Before;
@@ -165,10 +168,14 @@
                         mock(NotificationFilter.class),
                         mock(NotifLog.class),
                         mock(NotificationSectionsFeatureManager.class),
-                        mock(PeopleNotificationIdentifier.class)
+                        mock(PeopleNotificationIdentifier.class),
+                        mock(HighPriorityProvider.class)
                 ),
                 mock(NotificationEntryManager.KeyguardEnvironment.class),
-                mock(FeatureFlags.class));
+                mock(FeatureFlags.class),
+                () -> mock(NotificationRowBinder.class),
+                () -> mRemoteInputManager,
+                mock(LeakDetector.class));
         mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
         mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index 39afbe0..8f645b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -190,7 +190,7 @@
         mFragments.dispatchResume();
         processAllMessages();
 
-        verify(mBroadcastDispatcher).registerReceiver(
+        verify(mBroadcastDispatcher).registerReceiverWithHandler(
                 any(BroadcastReceiver.class),
                 any(IntentFilter.class),
                 any(Handler.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 7e485f4..560aadb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -120,6 +120,7 @@
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -255,6 +256,7 @@
     @Mock private KeyguardDismissUtil mKeyguardDismissUtil;
     @Mock private ExtensionController mExtensionController;
     @Mock private UserInfoControllerImpl mUserInfoControllerImpl;
+    @Mock private NotificationRowBinderImpl mNotificationRowBinder;
     private ShadeController mShadeController;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
     private InitController mInitController = new InitController();
@@ -344,7 +346,6 @@
                 mHeadsUpManager,
                 mDynamicPrivacyController,
                 mBypassHeadsUpNotifier,
-                true,
                 () -> mNewNotifPipeline,
                 new FalsingManagerFake(),
                 mBroadcastDispatcher,
@@ -413,6 +414,7 @@
                 mKeyguardDismissUtil,
                 mExtensionController,
                 mUserInfoControllerImpl,
+                mNotificationRowBinder,
                 mDismissCallbackRegistry);
 
         when(mStatusBarWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index c4caeb3..9cb06a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -180,7 +180,7 @@
     protected void setupNetworkController() {
         // For now just pretend to be the data sim, so we can test that too.
         mSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
-        when(mMockTm.isDataCapable()).thenReturn(true);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(true);
         setDefaultSubId(mSubId);
         setSubscriptions(mSubId);
         mMobileSignalController = mNetworkController.mMobileSignalControllers.get(mSubId);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 95b055c..5a5ef8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -121,7 +121,7 @@
     @Test
     public void testNoInternetIcon_withDefaultSub() {
         setupNetworkController();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         setupDefaultSignal();
         updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0);
         setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -135,7 +135,7 @@
     @Test
     public void testDataDisabledIcon_withDefaultSub() {
         setupNetworkController();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         setupDefaultSignal();
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
         setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -149,7 +149,7 @@
     @Test
     public void testNonDefaultSIM_showsFullSignal_connected() {
         setupNetworkController();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         setupDefaultSignal();
         setDefaultSubId(mSubId + 1);
         updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0);
@@ -164,7 +164,7 @@
     @Test
     public void testNonDefaultSIM_showsFullSignal_disconnected() {
         setupNetworkController();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         setupDefaultSignal();
         setDefaultSubId(mSubId + 1);
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
@@ -429,7 +429,7 @@
     @Test
     public void testDataDisabledIcon_UserNotSetup() {
         setupNetworkController();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         setupDefaultSignal();
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
         setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -444,7 +444,7 @@
     @Test
     public void testAlwaysShowDataRatIcon() {
         setupDefaultSignal();
-        when(mMockTm.isDataCapable()).thenReturn(false);
+        when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
         updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED,
                 TelephonyManager.NETWORK_TYPE_GSM);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 2854665..80aa6f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -69,7 +69,7 @@
 
     @Test
     public void testRegisteredWithDispatcher() {
-        verify(mBroadcastDispatcher).registerReceiver(any(BroadcastReceiver.class),
+        verify(mBroadcastDispatcher).registerReceiverWithHandler(any(BroadcastReceiver.class),
                 any(IntentFilter.class),
                 any(Handler.class)); // VolumeDialogControllerImpl does not call with user
     }
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index 797b713..d297f3f 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -123,4 +123,5 @@
     use_embedded_native_libs: true,
     // The permission configuration *must* be included to ensure security of the device
     required: ["NetworkPermissionConfig"],
+    apex_available: ["com.android.tethering"],
 }
diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml
index 87a8c3f..e99c2c5 100644
--- a/packages/Tethering/AndroidManifest.xml
+++ b/packages/Tethering/AndroidManifest.xml
@@ -33,6 +33,7 @@
     <uses-permission android:name="android.permission.MANAGE_USB" />
     <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
     <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
+    <uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
     <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
 
diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp
index 5785707..264ce44 100644
--- a/packages/Tethering/common/TetheringLib/Android.bp
+++ b/packages/Tethering/common/TetheringLib/Android.bp
@@ -47,6 +47,16 @@
     libs: [
         "android_system_stubs_current",
     ],
+
+    hostdex: true, // for hiddenapi check
+    visibility: [
+        "//frameworks/base/packages/Tethering:__subpackages__",
+        //TODO(b/147200698) remove below lines when the platform is built with stubs
+        "//frameworks/base",
+        "//frameworks/base/services",
+        "//frameworks/base/services/core",
+    ],
+    apex_available: ["com.android.tethering"],
 }
 
 filegroup {
diff --git a/packages/Tethering/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/UpstreamNetworkMonitor.java b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index fe3f517..5692a6f 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -16,9 +16,12 @@
 
 package com.android.server.connectivity.tethering;
 
+import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
-import static android.net.ConnectivityManager.TYPE_NONE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -35,8 +38,10 @@
 import android.net.util.SharedLog;
 import android.os.Handler;
 import android.util.Log;
+import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.StateMachine;
 
 import java.util.HashMap;
@@ -77,11 +82,25 @@
     public static final int EVENT_ON_LINKPROPERTIES = 2;
     public static final int EVENT_ON_LOST           = 3;
     public static final int NOTIFY_LOCAL_PREFIXES   = 10;
+    // This value is used by deprecated preferredUpstreamIfaceTypes selection which is default
+    // disabled.
+    @VisibleForTesting
+    public static final int TYPE_NONE = -1;
 
     private static final int CALLBACK_LISTEN_ALL = 1;
     private static final int CALLBACK_DEFAULT_INTERNET = 2;
     private static final int CALLBACK_MOBILE_REQUEST = 3;
 
+    private static final SparseIntArray sLegacyTypeToTransport = new SparseIntArray();
+    static {
+        sLegacyTypeToTransport.put(TYPE_MOBILE,       NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_DUN,   NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_MOBILE_HIPRI, NetworkCapabilities.TRANSPORT_CELLULAR);
+        sLegacyTypeToTransport.put(TYPE_WIFI,         NetworkCapabilities.TRANSPORT_WIFI);
+        sLegacyTypeToTransport.put(TYPE_BLUETOOTH,    NetworkCapabilities.TRANSPORT_BLUETOOTH);
+        sLegacyTypeToTransport.put(TYPE_ETHERNET,     NetworkCapabilities.TRANSPORT_ETHERNET);
+    }
+
     private final Context mContext;
     private final SharedLog mLog;
     private final StateMachine mTarget;
@@ -202,7 +221,7 @@
         final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
 
         final NetworkRequest mobileUpstreamRequest = new NetworkRequest.Builder()
-                .setCapabilities(ConnectivityManager.networkCapabilitiesForType(legacyType))
+                .setCapabilities(networkCapabilitiesForType(legacyType))
                 .build();
 
         // The existing default network and DUN callbacks will be notified.
@@ -354,16 +373,6 @@
         notifyTarget(EVENT_ON_LINKPROPERTIES, network);
     }
 
-    private void handleSuspended(Network network) {
-        if (!network.equals(mTetheringUpstreamNetwork)) return;
-        mLog.log("SUSPENDED current upstream: " + network);
-    }
-
-    private void handleResumed(Network network) {
-        if (!network.equals(mTetheringUpstreamNetwork)) return;
-        mLog.log("RESUMED current upstream: " + network);
-    }
-
     private void handleLost(Network network) {
         // There are few TODOs within ConnectivityService's rematching code
         // pertaining to spurious onLost() notifications.
@@ -453,20 +462,6 @@
         }
 
         @Override
-        public void onNetworkSuspended(Network network) {
-            if (mCallbackType == CALLBACK_LISTEN_ALL) {
-                handleSuspended(network);
-            }
-        }
-
-        @Override
-        public void onNetworkResumed(Network network) {
-            if (mCallbackType == CALLBACK_LISTEN_ALL) {
-                handleResumed(network);
-            }
-        }
-
-        @Override
         public void onLost(Network network) {
             if (mCallbackType == CALLBACK_DEFAULT_INTERNET) {
                 mDefaultInternetNetwork = null;
@@ -510,7 +505,7 @@
         for (int type : preferredTypes) {
             NetworkCapabilities nc;
             try {
-                nc = ConnectivityManager.networkCapabilitiesForType(type);
+                nc = networkCapabilitiesForType(type);
             } catch (IllegalArgumentException iae) {
                 Log.e(TAG, "No NetworkCapabilities mapping for legacy type: " + type);
                 continue;
@@ -572,4 +567,28 @@
 
         return null;
     }
+
+    /**
+     * Given a legacy type (TYPE_WIFI, ...) returns the corresponding NetworkCapabilities instance.
+     * This function is used for deprecated legacy type and be disabled by default.
+     */
+    @VisibleForTesting
+    public static NetworkCapabilities networkCapabilitiesForType(int type) {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+
+        // Map from type to transports.
+        final int notFound = -1;
+        final int transport = sLegacyTypeToTransport.get(type, notFound);
+        Preconditions.checkArgument(transport != notFound, "unknown legacy type: " + type);
+        nc.addTransportType(transport);
+
+        if (type == TYPE_MOBILE_DUN) {
+            nc.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
+            // DUN is restricted network, see NetworkCapabilities#FORCE_RESTRICTED_CAPABILITIES.
+            nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        } else {
+            nc.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        }
+        return nc;
+    }
 }
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index 7af48a8..271769e 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -224,6 +224,11 @@
             if (TelephonyManager.class.equals(serviceClass)) return Context.TELEPHONY_SERVICE;
             return super.getSystemServiceName(serviceClass);
         }
+
+        @Override
+        public Context createContextAsUser(UserHandle user, int flags) {
+            return mContext;
+        }
     }
 
     public class MockIpServerDependencies extends IpServer.Dependencies {
@@ -432,6 +437,7 @@
                 .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);
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index c90abbb..5ed75bf 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -18,13 +18,14 @@
 
 import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
 import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
-import static android.net.ConnectivityManager.TYPE_NONE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
+import static com.android.server.connectivity.tethering.UpstreamNetworkMonitor.TYPE_NONE;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -538,13 +539,15 @@
                 mUNM.selectPreferredUpstreamType(preferredTypes));
         verify(mEntitleMgr, times(1)).maybeRunProvisioning();
     }
+
     private void assertSatisfiesLegacyType(int legacyType, UpstreamNetworkState ns) {
         if (legacyType == TYPE_NONE) {
             assertTrue(ns == null);
             return;
         }
 
-        final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType);
+        final NetworkCapabilities nc =
+                UpstreamNetworkMonitor.networkCapabilitiesForType(legacyType);
         assertTrue(nc.satisfiedByNetworkCapabilities(ns.networkCapabilities));
     }
 
diff --git a/packages/services/PacProcessor/jni/Android.bp b/packages/services/PacProcessor/jni/Android.bp
index 2a94237..61f8143 100644
--- a/packages/services/PacProcessor/jni/Android.bp
+++ b/packages/services/PacProcessor/jni/Android.bp
@@ -37,4 +37,10 @@
         "-Wunused",
         "-Wunreachable-code",
     ],
+    sanitize: {
+        cfi: true,
+        diag: {
+            cfi: true,
+        },
+    },
 }
diff --git a/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/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 3bce322..b13bef2 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1406,10 +1406,7 @@
         long oldId = Binder.clearCallingIdentity();
         final int[] userIds;
         try {
-            userIds =
-                    mContext
-                            .getSystemService(UserManager.class)
-                            .getProfileIds(callingUserId, false);
+            userIds = getUserManager().getProfileIds(callingUserId, false);
         } finally {
             Binder.restoreCallingIdentity(oldId);
         }
@@ -1452,6 +1449,11 @@
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
             return;
         }
+        dumpWithoutCheckingPermission(fd, pw, args);
+    }
+
+    @VisibleForTesting
+    void dumpWithoutCheckingPermission(FileDescriptor fd, PrintWriter pw, String[] args) {
         int userId = binderGetCallingUserId();
         if (!isUserReadyForBackup(userId)) {
             pw.println("Inactive");
@@ -1460,7 +1462,16 @@
 
         if (args != null) {
             for (String arg : args) {
-                if ("users".equals(arg.toLowerCase())) {
+                if ("-h".equals(arg)) {
+                    pw.println("'dumpsys backup' optional arguments:");
+                    pw.println("  -h       : this help text");
+                    pw.println("  a[gents] : dump information about defined backup agents");
+                    pw.println("  transportclients : dump information about transport clients");
+                    pw.println("  transportstats : dump transport statts");
+                    pw.println("  users    : dump the list of users for which backup service "
+                            + "is running");
+                    return;
+                } else if ("users".equals(arg.toLowerCase())) {
                     pw.print(DUMP_RUNNING_USERS_MESSAGE);
                     for (int i = 0; i < mUserServices.size(); i++) {
                         pw.print(" " + mUserServices.keyAt(i));
@@ -1471,11 +1482,12 @@
             }
         }
 
-        UserBackupManagerService userBackupManagerService =
-                getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "dump()");
-
-        if (userBackupManagerService != null) {
-            userBackupManagerService.dump(fd, pw, args);
+        for (int i = 0; i < mUserServices.size(); i++) {
+            UserBackupManagerService userBackupManagerService =
+                    getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), "dump()");
+            if (userBackupManagerService != null) {
+                userBackupManagerService.dump(fd, pw, args);
+            }
         }
     }
 
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 064cd06..7b95ab5 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -3545,14 +3545,7 @@
         try {
             if (args != null) {
                 for (String arg : args) {
-                    if ("-h".equals(arg)) {
-                        pw.println("'dumpsys backup' optional arguments:");
-                        pw.println("  -h       : this help text");
-                        pw.println("  a[gents] : dump information about defined backup agents");
-                        pw.println("  users    : dump the list of users for which backup service "
-                                + "is running");
-                        return;
-                    } else if ("agents".startsWith(arg)) {
+                    if ("agents".startsWith(arg)) {
                         dumpAgents(pw);
                         return;
                     } else if ("transportclients".equals(arg.toLowerCase())) {
@@ -3583,8 +3576,10 @@
     }
 
     private void dumpInternal(PrintWriter pw) {
+        // Add prefix for only non-system users so that system user dumpsys is the same as before
+        String userPrefix = mUserId == UserHandle.USER_SYSTEM ? "" : "User " + mUserId + ":";
         synchronized (mQueueLock) {
-            pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled")
+            pw.println(userPrefix + "Backup Manager is " + (mEnabled ? "enabled" : "disabled")
                     + " / " + (!mSetupComplete ? "not " : "") + "setup complete / "
                     + (this.mPendingInits.size() == 0 ? "not " : "") + "pending init");
             pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled"));
@@ -3594,13 +3589,13 @@
                     + " (now = " + System.currentTimeMillis() + ')');
             pw.println("  next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId));
 
-            pw.println("Transport whitelist:");
+            pw.println(userPrefix + "Transport whitelist:");
             for (ComponentName transport : mTransportManager.getTransportWhitelist()) {
                 pw.print("    ");
                 pw.println(transport.flattenToShortString());
             }
 
-            pw.println("Available transports:");
+            pw.println(userPrefix + "Available transports:");
             final String[] transports = listAllTransports();
             if (transports != null) {
                 for (String t : transports) {
@@ -3626,18 +3621,18 @@
 
             mTransportManager.dumpTransportClients(pw);
 
-            pw.println("Pending init: " + mPendingInits.size());
+            pw.println(userPrefix + "Pending init: " + mPendingInits.size());
             for (String s : mPendingInits) {
                 pw.println("    " + s);
             }
 
-            pw.print("Ancestral: ");
+            pw.print(userPrefix + "Ancestral: ");
             pw.println(Long.toHexString(mAncestralToken));
-            pw.print("Current:   ");
+            pw.print(userPrefix + "Current:   ");
             pw.println(Long.toHexString(mCurrentToken));
 
             int numPackages = mBackupParticipants.size();
-            pw.println("Participants:");
+            pw.println(userPrefix + "Participants:");
             for (int i = 0; i < numPackages; i++) {
                 int uid = mBackupParticipants.keyAt(i);
                 pw.print("  uid: ");
@@ -3648,7 +3643,7 @@
                 }
             }
 
-            pw.println("Ancestral packages: "
+            pw.println(userPrefix + "Ancestral packages: "
                     + (mAncestralPackages == null ? "none" : mAncestralPackages.size()));
             if (mAncestralPackages != null) {
                 for (String pkg : mAncestralPackages) {
@@ -3657,17 +3652,17 @@
             }
 
             Set<String> processedPackages = mProcessedPackagesJournal.getPackagesCopy();
-            pw.println("Ever backed up: " + processedPackages.size());
+            pw.println(userPrefix + "Ever backed up: " + processedPackages.size());
             for (String pkg : processedPackages) {
                 pw.println("    " + pkg);
             }
 
-            pw.println("Pending key/value backup: " + mPendingBackups.size());
+            pw.println(userPrefix + "Pending key/value backup: " + mPendingBackups.size());
             for (BackupRequest req : mPendingBackups.values()) {
                 pw.println("    " + req);
             }
 
-            pw.println("Full backup queue:" + mFullBackupQueue.size());
+            pw.println(userPrefix + "Full backup queue:" + mFullBackupQueue.size());
             for (FullBackupEntry entry : mFullBackupQueue) {
                 pw.print("    ");
                 pw.print(entry.lastBackup);
diff --git a/services/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/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java
index 70569db..5179fa7 100644
--- a/services/core/java/com/android/server/GraphicsStatsService.java
+++ b/services/core/java/com/android/server/GraphicsStatsService.java
@@ -38,11 +38,13 @@
 import android.view.IGraphicsStatsCallback;
 
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FastPrintWriter;
 
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -78,6 +80,8 @@
     private static final int SAVE_BUFFER = 1;
     private static final int DELETE_OLD = 2;
 
+    private static final int AID_STATSD = 1066; // Statsd uid is set to 1066 forever.
+
     // This isn't static because we need this to happen after registerNativeMethods, however
     // the class is loaded (and thus static ctor happens) before that occurs.
     private final int ASHMEM_SIZE = nGetAshmemSize();
@@ -121,6 +125,7 @@
                 return true;
             }
         });
+        nativeInit();
     }
 
     /**
@@ -186,6 +191,86 @@
         return pfd;
     }
 
+    // If lastFullDay is true, pullGraphicsStats returns stats for the last complete day/24h period
+    // that does not include today. If lastFullDay is false, pullGraphicsStats returns stats for the
+    // current day.
+    // This method is invoked from native code only.
+    @SuppressWarnings({"UnusedDeclaration"})
+    private long pullGraphicsStats(boolean lastFullDay) throws RemoteException {
+        int uid = Binder.getCallingUid();
+
+        // DUMP and PACKAGE_USAGE_STATS permissions are required to invoke this method.
+        // TODO: remove exception for statsd daemon after required permissions are granted. statsd
+        // TODO: should have these permissions granted by data/etc/platform.xml, but it does not.
+        if (uid != AID_STATSD) {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new FastPrintWriter(sw);
+            if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
+                pw.flush();
+                throw new RemoteException(sw.toString());
+            }
+        }
+
+        long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            return pullGraphicsStatsImpl(lastFullDay);
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+    }
+
+    private long pullGraphicsStatsImpl(boolean lastFullDay) {
+        long targetDay;
+        if (lastFullDay) {
+            // Get stats from yesterday. Stats stay constant, because the day is over.
+            targetDay = normalizeDate(System.currentTimeMillis() - 86400000).getTimeInMillis();
+        } else {
+            // Get stats from today. Stats may change as more apps are run today.
+            targetDay = normalizeDate(System.currentTimeMillis()).getTimeInMillis();
+        }
+
+        // Find active buffers for targetDay.
+        ArrayList<HistoricalBuffer> buffers;
+        synchronized (mLock) {
+            buffers = new ArrayList<>(mActive.size());
+            for (int i = 0; i < mActive.size(); i++) {
+                ActiveBuffer buffer = mActive.get(i);
+                if (buffer.mInfo.startTime == targetDay) {
+                    try {
+                        buffers.add(new HistoricalBuffer(buffer));
+                    } catch (IOException ex) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+
+        // Dump active and historic buffers for targetDay in a serialized
+        // GraphicsStatsServiceDumpProto proto.
+        long dump = nCreateDump(-1, true);
+        try {
+            synchronized (mFileAccessLock) {
+                HashSet<File> skipList = dumpActiveLocked(dump, buffers);
+                buffers.clear();
+                String subPath = String.format("%d", targetDay);
+                File dateDir = new File(mGraphicsStatsDir, subPath);
+                if (dateDir.exists()) {
+                    for (File pkg : dateDir.listFiles()) {
+                        for (File version : pkg.listFiles()) {
+                            File data = new File(version, "total");
+                            if (skipList.contains(data)) {
+                                continue;
+                            }
+                            nAddToDump(dump, data.getAbsolutePath());
+                        }
+                    }
+                }
+            }
+        } finally {
+            return nFinishDumpInMemory(dump);
+        }
+    }
+
     private ParcelFileDescriptor getPfd(MemoryFile file) {
         try {
             if (!file.getFileDescriptor().valid()) {
@@ -379,12 +464,21 @@
         }
     }
 
+    @Override
+    protected void finalize() throws Throwable {
+        nativeDestructor();
+    }
+
+    private native void nativeInit();
+    private static native void nativeDestructor();
+
     private static native int nGetAshmemSize();
     private static native long nCreateDump(int outFd, boolean isProto);
     private static native void nAddToDump(long dump, String path, String packageName,
             long versionCode, long startTime, long endTime, byte[] data);
     private static native void nAddToDump(long dump, String path);
     private static native void nFinishDump(long dump);
+    private static native long nFinishDumpInMemory(long dump);
     private static native void nSaveBuffer(String path, String packageName, long versionCode,
             long startTime, long endTime, byte[] data);
 
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 32128d5..dc393d1 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -69,7 +69,6 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.os.WorkSource;
 import android.os.WorkSource.WorkChain;
 import android.stats.location.LocationStatsEnums;
@@ -84,7 +83,6 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.location.ProviderProperties;
 import com.android.internal.location.ProviderRequest;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
@@ -105,6 +103,7 @@
 import com.android.server.location.MockProvider;
 import com.android.server.location.MockableLocationProvider;
 import com.android.server.location.PassiveProvider;
+import com.android.server.location.UserInfoStore;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
 import java.io.ByteArrayOutputStream;
@@ -194,6 +193,7 @@
     private final Object mLock = new Object();
     private final Context mContext;
     private final Handler mHandler;
+    private final UserInfoStore mUserInfoStore;
     private final LocationSettingsStore mSettingsStore;
     private final LocationUsageLogger mLocationUsageLogger;
 
@@ -203,7 +203,6 @@
     private PackageManager mPackageManager;
     private PowerManager mPowerManager;
     private ActivityManager mActivityManager;
-    private UserManager mUserManager;
 
     private GeofenceManager mGeofenceManager;
     private LocationFudger mLocationFudger;
@@ -237,10 +236,6 @@
     private final HashMap<String, Location> mLastLocationCoarseInterval =
             new HashMap<>();
 
-    // current active user on the device
-    private int mCurrentUserId;
-    private int[] mCurrentUserProfiles;
-
     @GuardedBy("mLock")
     @PowerManager.LocationPowerSaveMode
     private int mBatterySaverMode;
@@ -248,12 +243,10 @@
     private LocationManagerService(Context context) {
         mContext = context;
         mHandler = FgThread.getHandler();
+        mUserInfoStore = new UserInfoStore(mContext);
         mSettingsStore = new LocationSettingsStore(mContext, mHandler);
         mLocationUsageLogger = new LocationUsageLogger();
 
-        mCurrentUserId = UserHandle.USER_NULL;
-        mCurrentUserProfiles = new int[]{UserHandle.USER_NULL};
-
         // set up passive provider -  we do this early because it has no dependencies on system
         // services or external code that isn't ready yet, and because this allows the variable to
         // be final. other more complex providers are initialized later, when system services are
@@ -277,6 +270,7 @@
     }
 
     private void onSystemReady() {
+        mUserInfoStore.onSystemReady();
         mSettingsStore.onSystemReady();
 
         synchronized (mLock) {
@@ -284,7 +278,6 @@
             mAppOps = mContext.getSystemService(AppOpsManager.class);
             mPowerManager = mContext.getSystemService(PowerManager.class);
             mActivityManager = mContext.getSystemService(ActivityManager.class);
-            mUserManager = mContext.getSystemService(UserManager.class);
 
             mLocationFudger = new LocationFudger(mContext, mHandler);
             mGeofenceManager = new GeofenceManager(mContext, mSettingsStore);
@@ -372,10 +365,13 @@
                 }
             }.register(mContext, mHandler.getLooper(), true);
 
+            mUserInfoStore.addListener((oldUserId, newUserId) -> {
+                synchronized (mLock) {
+                    onUserChangedLocked(oldUserId, newUserId);
+                }
+            });
+
             IntentFilter intentFilter = new IntentFilter();
-            intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
-            intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
-            intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
             intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
             intentFilter.addAction(Intent.ACTION_SCREEN_ON);
 
@@ -388,14 +384,6 @@
                     }
                     synchronized (mLock) {
                         switch (action) {
-                            case Intent.ACTION_USER_SWITCHED:
-                                onUserChangedLocked(
-                                        intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
-                                break;
-                            case Intent.ACTION_MANAGED_PROFILE_ADDED:
-                            case Intent.ACTION_MANAGED_PROFILE_REMOVED:
-                                onUserProfilesChangedLocked();
-                                break;
                             case Intent.ACTION_SCREEN_ON:
                             case Intent.ACTION_SCREEN_OFF:
                                 onScreenStateChangedLocked();
@@ -405,11 +393,10 @@
                 }
             }, UserHandle.ALL, intentFilter, null, mHandler);
 
-            // switching the user from null to system here performs the bulk of the initialization
+            // switching the user from null to current here performs the bulk of the initialization
             // work. the user being changed will cause a reload of all user specific settings, which
             // causes initialization, and propagates changes until a steady state is reached
-            mCurrentUserId = UserHandle.USER_NULL;
-            onUserChangedLocked(ActivityManager.getCurrentUser());
+            onUserChangedLocked(UserHandle.USER_NULL, mUserInfoStore.getCurrentUserId());
         }
     }
 
@@ -551,16 +538,6 @@
     }
 
     @GuardedBy("mLock")
-    private void onUserProfilesChangedLocked() {
-        mCurrentUserProfiles = mUserManager.getProfileIdsWithDisabled(mCurrentUserId);
-    }
-
-    @GuardedBy("mLock")
-    private boolean isCurrentProfileLocked(int userId) {
-        return ArrayUtils.contains(mCurrentUserProfiles, userId);
-    }
-
-    @GuardedBy("mLock")
     private void ensureFallbackFusedProviderPresentLocked(String[] pkgs) {
         PackageManager pm = mContext.getPackageManager();
         String systemPackageName = mContext.getPackageName();
@@ -568,7 +545,7 @@
 
         List<ResolveInfo> rInfos = pm.queryIntentServicesAsUser(
                 new Intent(FUSED_LOCATION_SERVICE_ACTION),
-                PackageManager.GET_META_DATA, mCurrentUserId);
+                PackageManager.GET_META_DATA, mUserInfoStore.getCurrentUserId());
         for (ResolveInfo rInfo : rInfos) {
             String packageName = rInfo.serviceInfo.packageName;
 
@@ -752,27 +729,18 @@
     }
 
     @GuardedBy("mLock")
-    private void onUserChangedLocked(int userId) {
-        if (mCurrentUserId == userId) {
-            return;
-        }
-
+    private void onUserChangedLocked(int oldUserId, int newUserId) {
         if (D) {
-            Log.d(TAG, "foreground user is changing to " + userId);
+            Log.d(TAG, "foreground user is changing to " + newUserId);
         }
 
-        int oldUserId = mCurrentUserId;
-        mCurrentUserId = userId;
-        onUserProfilesChangedLocked();
-
-        // let providers know the current user has changed
         for (LocationProviderManager manager : mProviderManagers) {
             // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
             mSettingsStore.setLocationProviderAllowed(manager.getName(),
-                    manager.isUseable(mCurrentUserId), mCurrentUserId);
+                    manager.isUseable(newUserId), newUserId);
 
             manager.onUseableChangedLocked(oldUserId);
-            manager.onUseableChangedLocked(mCurrentUserId);
+            manager.onUseableChangedLocked(newUserId);
         }
     }
 
@@ -786,8 +754,12 @@
         // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
         protected final MockableLocationProvider mProvider;
 
+        // useable state for parent user ids, no entry implies false. location state is only kept
+        // for parent user ids, the location state for a profile user id is assumed to be the same
+        // as for the parent. if querying this structure, ensure that the user id being used is a
+        // parent id or the results may be incorrect.
         @GuardedBy("mLock")
-        private final SparseArray<Boolean> mUseable;  // combined state for each user id
+        private final SparseArray<Boolean> mUseable;
 
         private LocationProviderManager(String name) {
             mName = name;
@@ -795,6 +767,9 @@
 
             // initialize last since this lets our reference escape
             mProvider = new MockableLocationProvider(mContext, mLock, this);
+
+            // we can assume all users start with unuseable location state since the initial state
+            // of all providers is disabled. no need to initialize mUseable further.
         }
 
         public String getName() {
@@ -868,29 +843,6 @@
             mProvider.sendExtraCommand(uid, pid, command, extras);
         }
 
-        public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
-            synchronized (mLock) {
-                pw.print(mName + " provider");
-                if (mProvider.isMock()) {
-                    pw.print(" [mock]");
-                }
-                pw.println(":");
-
-                pw.increaseIndent();
-
-                pw.println("useable=" + isUseable(mCurrentUserId));
-                if (!isUseable(mCurrentUserId)) {
-                    pw.println("enabled=" + mProvider.getState().enabled);
-                }
-
-                pw.println("properties=" + mProvider.getState().properties);
-            }
-
-            mProvider.dump(fd, pw, args);
-
-            pw.decreaseIndent();
-        }
-
         @GuardedBy("mLock")
         @Override
         public void onReportLocation(Location location) {
@@ -927,18 +879,20 @@
                 // it would be more correct to call this for all users, but we know this can
                 // only affect the current user since providers are disabled for non-current
                 // users
-                onUseableChangedLocked(mCurrentUserId);
+                onUseableChangedLocked(mUserInfoStore.getCurrentUserId());
             }
         }
 
-        @GuardedBy("mLock")
         public boolean isUseable() {
-            return isUseable(mCurrentUserId);
+            return isUseable(mUserInfoStore.getCurrentUserId());
         }
 
-        @GuardedBy("mLock")
         public boolean isUseable(int userId) {
             synchronized (mLock) {
+                // normalize user id to always refer to parent since profile state is always the
+                // same as parent state
+                userId = mUserInfoStore.getParentUserId(userId);
+
                 return mUseable.get(userId, Boolean.FALSE);
             }
         }
@@ -950,15 +904,20 @@
                 return;
             }
 
+            // normalize user id to always refer to parent since profile state is always the same
+            // as parent state
+            userId = mUserInfoStore.getParentUserId(userId);
+
             // if any property that contributes to "useability" here changes state, it MUST result
             // in a direct or indrect call to onUseableChangedLocked. this allows the provider to
             // guarantee that it will always eventually reach the correct state.
-            boolean useable = isCurrentProfileLocked(userId)
+            boolean useable = (userId == mUserInfoStore.getCurrentUserId())
                     && mSettingsStore.isLocationEnabled(userId) && mProvider.getState().enabled;
 
             if (useable == isUseable(userId)) {
                 return;
             }
+
             mUseable.put(userId, useable);
 
             if (D) {
@@ -986,6 +945,30 @@
 
             updateProviderUseableLocked(this);
         }
+
+        public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
+            synchronized (mLock) {
+                pw.print(mName + " provider");
+                if (mProvider.isMock()) {
+                    pw.print(" [mock]");
+                }
+                pw.println(":");
+
+                pw.increaseIndent();
+
+                boolean useable = isUseable();
+                pw.println("useable=" + useable);
+                if (!useable) {
+                    pw.println("enabled=" + mProvider.getState().enabled);
+                }
+
+                pw.println("properties=" + mProvider.getState().properties);
+            }
+
+            mProvider.dump(fd, pw, args);
+
+            pw.decreaseIndent();
+        }
     }
 
     class PassiveLocationProviderManager extends LocationProviderManager {
@@ -1626,7 +1609,7 @@
         ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
         if (records != null) {
             for (UpdateRecord record : records) {
-                if (!isCurrentProfileLocked(
+                if (!mUserInfoStore.isCurrentUserOrProfile(
                         UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
                     continue;
                 }
@@ -1691,7 +1674,7 @@
             // initialize the low power mode to true and set to false if any of the records requires
             providerRequest.setLowPowerMode(true);
             for (UpdateRecord record : records) {
-                if (!isCurrentProfileLocked(
+                if (!mUserInfoStore.isCurrentUserOrProfile(
                         UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
                     continue;
                 }
@@ -1750,7 +1733,7 @@
                 // TODO: overflow
                 long thresholdInterval = (providerRequest.getInterval() + 1000) * 3 / 2;
                 for (UpdateRecord record : records) {
-                    if (isCurrentProfileLocked(
+                    if (mUserInfoStore.isCurrentUserOrProfile(
                             UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
                         LocationRequest locationRequest = record.mRequest;
 
@@ -2243,8 +2226,8 @@
                 if (manager == null) return null;
 
                 // only the current user or location providers may get location this way
-                if (!isCurrentProfileLocked(UserHandle.getUserId(uid)) && !isProviderPackage(
-                        packageName)) {
+                if (!mUserInfoStore.isCurrentUserOrProfile(UserHandle.getUserId(uid))
+                        && !isProviderPackage(packageName)) {
                     return null;
                 }
 
@@ -2773,12 +2756,11 @@
             }
 
             int receiverUserId = UserHandle.getUserId(receiver.mCallerIdentity.mUid);
-            if (!isCurrentProfileLocked(receiverUserId)
+            if (!mUserInfoStore.isCurrentUserOrProfile(receiverUserId)
                     && !isProviderPackage(receiver.mCallerIdentity.mPackageName)) {
                 if (D) {
                     Log.d(TAG, "skipping loc update for background user " + receiverUserId +
-                            " (current user: " + mCurrentUserId + ", app: " +
-                            receiver.mCallerIdentity.mPackageName + ")");
+                            " (app: " + receiver.mCallerIdentity.mPackageName + ")");
                 }
                 continue;
             }
@@ -3028,9 +3010,17 @@
                     + TimeUtils.logTimeOfDay(System.currentTimeMillis()));
             ipw.println(", Current Elapsed Time: "
                     + TimeUtils.formatDuration(SystemClock.elapsedRealtime()));
-            ipw.println("Current user: " + mCurrentUserId + " " + Arrays.toString(
-                    mCurrentUserProfiles));
-            ipw.println("Location Mode: " + isLocationEnabledForUser(mCurrentUserId));
+
+            ipw.println("User Info:");
+            ipw.increaseIndent();
+            mUserInfoStore.dump(fd, ipw, args);
+            ipw.decreaseIndent();
+
+            ipw.println("Location Settings:");
+            ipw.increaseIndent();
+            mSettingsStore.dump(fd, ipw, args);
+            ipw.decreaseIndent();
+
             ipw.println("Battery Saver Location Mode: "
                     + locationPowerSaveModeToString(mBatterySaverMode));
 
@@ -3096,21 +3086,14 @@
                 mLocationFudger.dump(fd, ipw, args);
                 ipw.decreaseIndent();
             }
-        }
 
-        ipw.println("Location Settings:");
-        ipw.increaseIndent();
-        mSettingsStore.dump(fd, ipw, args);
-        ipw.decreaseIndent();
+            ipw.println("Location Providers:");
+            ipw.increaseIndent();
+            for (LocationProviderManager manager : mProviderManagers) {
+                manager.dump(fd, ipw, args);
+            }
+            ipw.decreaseIndent();
 
-        ipw.println("Location Providers:");
-        ipw.increaseIndent();
-        for (LocationProviderManager manager : mProviderManagers) {
-            manager.dump(fd, ipw, args);
-        }
-        ipw.decreaseIndent();
-
-        synchronized (mLock) {
             if (mGnssManagerService != null) {
                 ipw.println("GNSS:");
                 ipw.increaseIndent();
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 3e6ccb5..2b7745b 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -83,7 +83,6 @@
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.IPhoneStateListener;
 import com.android.internal.telephony.ITelephonyRegistry;
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
@@ -2194,6 +2193,17 @@
     private static final String PHONE_CONSTANTS_STATE_KEY = "state";
     private static final String PHONE_CONSTANTS_SUBSCRIPTION_KEY = "subscription";
 
+    /**
+     * Broadcast Action: The phone's signal strength has changed. The intent will have the
+     * following extra values:
+     *   phoneName - A string version of the phone name.
+     *   asu - A numeric value for the signal strength.
+     *         An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu).
+     *         The following special values are defined:
+     *         0 means "-113 dBm or less".31 means "-51 dBm or greater".
+     */
+    public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR";
+
     private void broadcastServiceStateChanged(ServiceState state, int phoneId, int subId) {
         long ident = Binder.clearCallingIdentity();
         try {
@@ -2228,7 +2238,7 @@
             Binder.restoreCallingIdentity(ident);
         }
 
-        Intent intent = new Intent(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED);
+        Intent intent = new Intent(ACTION_SIGNAL_STRENGTH_CHANGED);
         Bundle data = new Bundle();
         fillInSignalStrengthNotifierBundle(signalStrength, data);
         intent.putExtras(data);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index a58bd9b..e2a036a 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2068,7 +2068,7 @@
                 + " type=" + resolvedType + " callingUid=" + callingUid);
 
         userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
-                ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE, "service",
+                ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE, "service",
                 callingPackage);
 
         ServiceMap smap = getServiceMapLocked(userId);
diff --git a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
index ebfc2a0..549051d 100644
--- a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
@@ -32,6 +32,7 @@
 import android.graphics.RectF;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.os.UserManager;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -81,8 +82,15 @@
                     .setImageDrawable(drawable);
         }
 
-        ((TextView) view.findViewById(R.id.user_loading))
-                .setText(res.getString(R.string.car_loading_profile));
+        TextView msgView = view.findViewById(R.id.user_loading);
+        // TODO: use developer settings instead
+        if (Build.IS_DEBUGGABLE) {
+            // TODO: use specific string
+            msgView.setText(res.getString(R.string.car_loading_profile) + " user\n(from "
+                    + mOldUser.id + " to " + mNewUser.id + ")");
+        } else {
+            msgView.setText(res.getString(R.string.car_loading_profile));
+        }
         setView(view);
     }
 
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 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/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index eb1ab38..2b20782 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -16,12 +16,14 @@
 
 package com.android.server.am;
 
+import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM;
 import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
 import static android.app.ActivityManager.USER_OP_IS_CURRENT;
 import static android.app.ActivityManager.USER_OP_SUCCESS;
+import static android.app.ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE;
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
@@ -1641,9 +1643,10 @@
 
         if (callingUid != 0 && callingUid != SYSTEM_UID) {
             final boolean allow;
+            final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, targetUserId);
             if (mInjector.isCallerRecents(callingUid)
                     && callingUserId == getCurrentUserId()
-                    && isSameProfileGroup(callingUserId, targetUserId)) {
+                    && isSameProfileGroup) {
                 // If the caller is Recents and it is running in the current user, we then allow it
                 // to access its profiles.
                 allow = true;
@@ -1654,6 +1657,9 @@
             } else if (allowMode == ALLOW_FULL_ONLY) {
                 // We require full access, sucks to be you.
                 allow = false;
+            } else if (canInteractWithAcrossProfilesPermission(
+                    allowMode, isSameProfileGroup, callingPid, callingUid)) {
+                allow = true;
             } else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
                     callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
                 // If the caller does not have either permission, they are always doomed.
@@ -1661,10 +1667,11 @@
             } else if (allowMode == ALLOW_NON_FULL) {
                 // We are blanket allowing non-full access, you lucky caller!
                 allow = true;
-            } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
+            } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE
+                        || allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
                 // We may or may not allow this depending on whether the two users are
                 // in the same profile.
-                allow = isSameProfileGroup(callingUserId, targetUserId);
+                allow = isSameProfileGroup;
             } else {
                 throw new IllegalArgumentException("Unknown mode: " + allowMode);
             }
@@ -1690,6 +1697,11 @@
                     if (allowMode != ALLOW_FULL_ONLY) {
                         builder.append(" or ");
                         builder.append(INTERACT_ACROSS_USERS);
+                        if (isSameProfileGroup
+                                && allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
+                            builder.append(" or ");
+                            builder.append(INTERACT_ACROSS_PROFILES);
+                        }
                     }
                     String msg = builder.toString();
                     Slog.w(TAG, msg);
@@ -1710,6 +1722,19 @@
         return targetUserId;
     }
 
+    private boolean canInteractWithAcrossProfilesPermission(
+            int allowMode, boolean isSameProfileGroup, int callingPid, int callingUid) {
+        if (allowMode != ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
+            return false;
+        }
+        if (!isSameProfileGroup) {
+            return false;
+        }
+        return mInjector.checkComponentPermission(
+                INTERACT_ACROSS_PROFILES, callingPid, callingUid, /*owningUid= */-1,
+                /*exported= */true) == PackageManager.PERMISSION_GRANTED;
+    }
+
     int unsafeConvertIncomingUser(@UserIdInt int userId) {
         return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
                 ? getCurrentUserId(): userId;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index bc4dc8f..cd2272a 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1700,6 +1700,14 @@
         }
     }
 
+    /** @see AudioManager#getDevicesForAttributes(AudioAttributes) */
+    public @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes(
+            @NonNull AudioAttributes attributes) {
+        Objects.requireNonNull(attributes);
+        enforceModifyAudioRoutingPermission();
+        return AudioSystem.getDevicesForAttributes(attributes);
+    }
+
 
     /** @see AudioManager#adjustVolume(int, int) */
     public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index aea6d8d..f636d67 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -116,7 +116,8 @@
                 && !lp.hasIpv4Address();
 
         // If the network tells us it doesn't use clat, respect that.
-        final boolean skip464xlat = (nai.netMisc() != null) && nai.netMisc().skip464xlat;
+        final boolean skip464xlat = (nai.netAgentConfig() != null)
+                && nai.netAgentConfig().skip464xlat;
 
         return supported && connected && isIpv6OnlyNetwork && !skip464xlat;
     }
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 5e085ca..c1ab551 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -23,9 +23,9 @@
 import android.net.INetworkMonitor;
 import android.net.LinkProperties;
 import android.net.Network;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
-import android.net.NetworkMisc;
 import android.net.NetworkMonitorManager;
 import android.net.NetworkRequest;
 import android.net.NetworkScore;
@@ -127,7 +127,7 @@
     // This should only be modified by ConnectivityService, via setNetworkCapabilities().
     // TODO: make this private with a getter.
     public NetworkCapabilities networkCapabilities;
-    public final NetworkMisc networkMisc;
+    public final NetworkAgentConfig networkAgentConfig;
     // Indicates if netd has been told to create this Network. From this point on the appropriate
     // routing rules are setup and routes are added so packets can begin flowing over the Network.
     // This is a sticky bit; once set it is never cleared.
@@ -261,7 +261,7 @@
 
     public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
             LinkProperties lp, NetworkCapabilities nc, @NonNull NetworkScore ns, Context context,
-            Handler handler, NetworkMisc misc, ConnectivityService connService, INetd netd,
+            Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd,
             IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber) {
         this.messenger = messenger;
         asyncChannel = ac;
@@ -274,7 +274,7 @@
         mConnService = connService;
         mContext = context;
         mHandler = handler;
-        networkMisc = misc;
+        networkAgentConfig = config;
         this.factorySerialNumber = factorySerialNumber;
     }
 
@@ -309,8 +309,8 @@
         return mConnService;
     }
 
-    public NetworkMisc netMisc() {
-        return networkMisc;
+    public NetworkAgentConfig netAgentConfig() {
+        return networkAgentConfig;
     }
 
     public Handler handler() {
@@ -487,7 +487,8 @@
         // selected and we're trying to see what its score could be. This ensures that we don't tear
         // down an explicitly selected network before the user gets a chance to prefer it when
         // a higher-scoring network (e.g., Ethernet) is available.
-        if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
+        if (networkAgentConfig.explicitlySelected
+                && (networkAgentConfig.acceptUnvalidated || pretendValidated)) {
             return ConnectivityConstants.EXPLICITLY_SELECTED_NETWORK_SCORE;
         }
 
@@ -533,7 +534,8 @@
         synchronized (this) {
             // Network objects are outwardly immutable so there is no point in duplicating.
             // Duplicating also precludes sharing socket factories and connection pools.
-            final String subscriberId = (networkMisc != null) ? networkMisc.subscriberId : null;
+            final String subscriberId = (networkAgentConfig != null)
+                    ? networkAgentConfig.subscriberId : null;
             return new NetworkState(new NetworkInfo(networkInfo),
                     new LinkProperties(linkProperties),
                     new NetworkCapabilities(networkCapabilities), network, subscriberId, null);
@@ -641,13 +643,13 @@
                 + "nc{" + networkCapabilities + "}  Score{" + getCurrentScore() + "}  "
                 + "everValidated{" + everValidated + "}  lastValidated{" + lastValidated + "}  "
                 + "created{" + created + "} lingering{" + isLingering() + "} "
-                + "explicitlySelected{" + networkMisc.explicitlySelected + "} "
-                + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} "
+                + "explicitlySelected{" + networkAgentConfig.explicitlySelected + "} "
+                + "acceptUnvalidated{" + networkAgentConfig.acceptUnvalidated + "} "
                 + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} "
                 + "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} "
                 + "captivePortalValidationPending{" + captivePortalValidationPending + "} "
                 + "partialConnectivity{" + partialConnectivity + "} "
-                + "acceptPartialConnectivity{" + networkMisc.acceptPartialConnectivity + "} "
+                + "acceptPartialConnectivity{" + networkAgentConfig.acceptPartialConnectivity + "} "
                 + "clat{" + clatd + "} "
                 + "}";
     }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 6b70e5f..476f3f8 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -53,11 +53,11 @@
 import android.net.LocalSocketAddress;
 import android.net.Network;
 import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
 import android.net.RouteInfo;
 import android.net.UidRange;
 import android.net.VpnService;
@@ -914,7 +914,7 @@
      * has certain changes, in which case this method would just return {@code false}.
      */
     private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) {
-        // NetworkMisc cannot be updated without registering a new NetworkAgent.
+        // NetworkAgentConfig cannot be updated without registering a new NetworkAgent.
         if (oldConfig.allowBypass != mConfig.allowBypass) {
             Log.i(TAG, "Handover not possible due to changes to allowBypass");
             return false;
@@ -947,8 +947,8 @@
 
         mNetworkInfo.setDetailedState(DetailedState.CONNECTING, null, null);
 
-        NetworkMisc networkMisc = new NetworkMisc();
-        networkMisc.allowBypass = mConfig.allowBypass && !mLockdown;
+        NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
+        networkAgentConfig.allowBypass = mConfig.allowBypass && !mLockdown;
 
         mNetworkCapabilities.setEstablishingVpnAppUid(Binder.getCallingUid());
         mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserHandle,
@@ -957,8 +957,8 @@
         try {
             mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE /* logtag */,
                     mNetworkInfo, mNetworkCapabilities, lp,
-                    ConnectivityConstants.VPN_DEFAULT_SCORE, networkMisc,
-                    NetworkFactory.SerialNumber.VPN) {
+                    ConnectivityConstants.VPN_DEFAULT_SCORE, networkAgentConfig,
+                    NetworkProvider.ID_VPN) {
                             @Override
                             public void unwanted() {
                                 // We are user controlled, not driven by NetworkRequest.
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index d7ae5b5..e39d6d5 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1305,21 +1305,13 @@
     }
 
     private SurfaceControl.ScreenshotGraphicBuffer screenshotInternal(int displayId) {
-        synchronized (mSyncRoot) {
-            final IBinder token = getDisplayToken(displayId);
-            if (token == null) {
-                return null;
-            }
-            final LogicalDisplay logicalDisplay = mLogicalDisplays.get(displayId);
-            if (logicalDisplay == null) {
-                return null;
-            }
-
-            final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked();
-            return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(),
-                    displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(),
-                    false /* useIdentityTransform */, 0 /* rotation */);
+        final IBinder token = getDisplayToken(displayId);
+        if (token == null) {
+            return null;
         }
+        return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(
+                        token, new Rect(), 0 /* width */, 0 /* height */,
+                        false /* useIdentityTransform */, 0 /* rotation */);
     }
 
     @VisibleForTesting
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/UserInfoStore.java b/services/core/java/com/android/server/location/UserInfoStore.java
new file mode 100644
index 0000000..550f51c
--- /dev/null
+++ b/services/core/java/com/android/server/location/UserInfoStore.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.os.Build;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Provides accessors and listeners for all user info.
+ */
+public class UserInfoStore {
+
+    /**
+     * Listener for current user changes.
+     */
+    public interface UserChangedListener {
+        /**
+         * Called when the current user changes.
+         */
+        void onUserChanged(@UserIdInt int oldUserId, @UserIdInt int newUserId);
+    }
+
+    private final Context mContext;
+    private final CopyOnWriteArrayList<UserChangedListener> mListeners;
+
+    @GuardedBy("this")
+    @Nullable
+    private UserManager mUserManager;
+
+    @GuardedBy("this")
+    @UserIdInt
+    private int mCurrentUserId;
+
+    @GuardedBy("this")
+    @UserIdInt
+    private int mCachedParentUserId;
+    @GuardedBy("this")
+    private int[] mCachedProfileUserIds;
+
+    public UserInfoStore(Context context) {
+        mContext = context;
+        mListeners = new CopyOnWriteArrayList<>();
+
+        mCurrentUserId = UserHandle.USER_NULL;
+        mCachedParentUserId = UserHandle.USER_NULL;
+        mCachedProfileUserIds = new int[]{UserHandle.USER_NULL};
+    }
+
+    /** Called when system is ready. */
+    public synchronized void onSystemReady() {
+        if (mUserManager != null) {
+            return;
+        }
+
+        mUserManager = mContext.getSystemService(UserManager.class);
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+        intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
+        intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+
+        mContext.registerReceiverAsUser(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (action == null) {
+                    return;
+                }
+                switch (action) {
+                    case Intent.ACTION_USER_SWITCHED:
+                        int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+                                UserHandle.USER_NULL);
+                        if (userId != UserHandle.USER_NULL) {
+                            onUserChanged(userId);
+                        }
+                        break;
+                    case Intent.ACTION_MANAGED_PROFILE_ADDED:
+                    case Intent.ACTION_MANAGED_PROFILE_REMOVED:
+                        onUserProfilesChanged();
+                        break;
+                }
+            }
+        }, UserHandle.ALL, intentFilter, null, FgThread.getHandler());
+
+        mCurrentUserId = ActivityManager.getCurrentUser();
+    }
+
+    /**
+     * Adds a listener for user changed events.
+     */
+    public void addListener(UserChangedListener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Removes a listener for user changed events.
+     */
+    public void removeListener(UserChangedListener listener) {
+        mListeners.remove(listener);
+    }
+
+    private void onUserChanged(@UserIdInt int newUserId) {
+        int oldUserId;
+        synchronized (this) {
+            if (newUserId == mCurrentUserId) {
+                return;
+            }
+
+            oldUserId = mCurrentUserId;
+            mCurrentUserId = newUserId;
+        }
+
+        for (UserChangedListener listener : mListeners) {
+            listener.onUserChanged(oldUserId, newUserId);
+        }
+    }
+
+    private synchronized void onUserProfilesChanged() {
+        // this intent is only sent to the current user
+        if (mCachedParentUserId == mCurrentUserId) {
+            mCachedParentUserId = UserHandle.USER_NULL;
+            mCachedProfileUserIds = null;
+        }
+    }
+
+    /**
+     * Returns the user id of the current user.
+     */
+    @UserIdInt
+    public synchronized int getCurrentUserId() {
+        return mCurrentUserId;
+    }
+
+    /**
+     * Returns true if the given user id is either the current user or a profile of the current
+     * user.
+     */
+    public synchronized boolean isCurrentUserOrProfile(@UserIdInt int userId) {
+        return userId == mCurrentUserId || ArrayUtils.contains(
+                getProfileUserIdsForParentUser(mCurrentUserId), userId);
+    }
+
+    /**
+     * Returns the parent user id of the given user id, or the user id itself if the user id either
+     * is a parent or has no profiles.
+     */
+    @UserIdInt
+    public synchronized int getParentUserId(@UserIdInt int userId) {
+        int parentUserId;
+        if (userId == mCachedParentUserId || ArrayUtils.contains(mCachedProfileUserIds, userId)) {
+            parentUserId = mCachedParentUserId;
+        } else {
+            Preconditions.checkState(mUserManager != null);
+
+            UserInfo userInfo = mUserManager.getProfileParent(userId);
+            if (userInfo != null) {
+                parentUserId = userInfo.id;
+            } else {
+                // getProfileParent() returns null if the userId is already the parent...
+                parentUserId = userId;
+            }
+
+            // force profiles into cache
+            getProfileUserIdsForParentUser(parentUserId);
+        }
+
+        return parentUserId;
+    }
+
+    @GuardedBy("this")
+    private int[] getProfileUserIdsForParentUser(@UserIdInt int parentUserId) {
+        Preconditions.checkState(mUserManager != null);
+
+        if (Build.IS_DEBUGGABLE) {
+            Preconditions.checkArgument(mUserManager.getProfileParent(parentUserId) == null);
+        }
+
+        if (parentUserId != mCachedParentUserId) {
+            mCachedParentUserId = parentUserId;
+            mCachedProfileUserIds = mUserManager.getProfileIdsWithDisabled(parentUserId);
+        }
+
+        return mCachedProfileUserIds;
+    }
+
+    /**
+     * Dump info for debugging.
+     */
+    public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Current User: " + mCurrentUserId + " " + Arrays.toString(
+                getProfileUserIdsForParentUser(mCurrentUserId)));
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index d2e54f9..46ea9d1 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -25,11 +25,13 @@
 import android.os.ServiceManager;
 import android.os.UserManager;
 import android.util.Slog;
+import android.util.StatsLog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.RebootEscrowListener;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -109,20 +111,50 @@
     }
 
     void loadRebootEscrowDataIfAvailable() {
-        IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
-        if (rebootEscrow == null) {
+        List<UserInfo> users = mUserManager.getUsers();
+        List<UserInfo> rebootEscrowUsers = new ArrayList<>();
+        for (UserInfo user : users) {
+            if (mCallbacks.isUserSecure(user.id) && mStorage.hasRebootEscrow(user.id)) {
+                rebootEscrowUsers.add(user);
+            }
+        }
+
+        if (rebootEscrowUsers.isEmpty()) {
             return;
         }
 
-        final SecretKeySpec escrowKey;
+        SecretKeySpec escrowKey = getAndClearRebootEscrowKey();
+        if (escrowKey == null) {
+            Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
+            for (UserInfo user : users) {
+                mStorage.removeRebootEscrow(user.id);
+            }
+            StatsLog.write(StatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, false);
+            return;
+        }
+
+        boolean allUsersUnlocked = true;
+        for (UserInfo user : rebootEscrowUsers) {
+            allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey);
+        }
+        StatsLog.write(StatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, allUsersUnlocked);
+    }
+
+    private SecretKeySpec getAndClearRebootEscrowKey() {
+        IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
+        if (rebootEscrow == null) {
+            return null;
+        }
+
         try {
             byte[] escrowKeyBytes = rebootEscrow.retrieveKey();
             if (escrowKeyBytes == null) {
-                return;
+                Slog.w(TAG, "Had reboot escrow data for users, but could not retrieve key");
+                return null;
             } else if (escrowKeyBytes.length != 32) {
                 Slog.e(TAG, "IRebootEscrow returned key of incorrect size "
                         + escrowKeyBytes.length);
-                return;
+                return null;
             }
 
             // Make sure we didn't get the null key.
@@ -132,29 +164,22 @@
             }
             if (zero == 0) {
                 Slog.w(TAG, "IRebootEscrow returned an all-zeroes key");
-                return;
+                return null;
             }
 
             // Overwrite the existing key with the null key
             rebootEscrow.storeKey(new byte[32]);
 
-            escrowKey = RebootEscrowData.fromKeyBytes(escrowKeyBytes);
+            return RebootEscrowData.fromKeyBytes(escrowKeyBytes);
         } catch (RemoteException e) {
             Slog.w(TAG, "Could not retrieve escrow data");
-            return;
-        }
-
-        List<UserInfo> users = mUserManager.getUsers();
-        for (UserInfo user : users) {
-            if (mCallbacks.isUserSecure(user.id)) {
-                restoreRebootEscrowForUser(user.id, escrowKey);
-            }
+            return null;
         }
     }
 
-    private void restoreRebootEscrowForUser(@UserIdInt int userId, SecretKeySpec escrowKey) {
+    private boolean restoreRebootEscrowForUser(@UserIdInt int userId, SecretKeySpec escrowKey) {
         if (!mStorage.hasRebootEscrow(userId)) {
-            return;
+            return false;
         }
 
         try {
@@ -165,9 +190,11 @@
 
             mCallbacks.onRebootEscrowRestored(escrowData.getSpVersion(),
                     escrowData.getSyntheticPassword(), userId);
+            return true;
         } catch (IOException e) {
             Slog.w(TAG, "Could not load reboot escrow data for user " + userId, e);
         }
+        return false;
     }
 
     void callToRebootEscrowIfNeeded(@UserIdInt int userId, byte spVersion,
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index 29338ba0..52750f3 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.security.Scrypt;
 import android.security.keystore.recovery.KeyChainProtectionParams;
 import android.security.keystore.recovery.KeyChainSnapshot;
@@ -163,16 +164,28 @@
     }
 
     private void syncKeys() throws RemoteException {
+        int generation = mPlatformKeyManager.getGenerationId(mUserId);
         if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
             // Application keys for the user will not be available for sync.
             Log.w(TAG, "Credentials are not set for user " + mUserId);
-            int generation = mPlatformKeyManager.getGenerationId(mUserId);
-            mPlatformKeyManager.invalidatePlatformKey(mUserId, generation);
+            if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED
+                    || mUserId != UserHandle.USER_SYSTEM) {
+                // Only invalidate keys with legacy protection param.
+                mPlatformKeyManager.invalidatePlatformKey(mUserId, generation);
+            }
             return;
         }
         if (isCustomLockScreen()) {
-            Log.w(TAG, "Unsupported credential type " + mCredentialType + "for user " + mUserId);
-            mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId);
+            Log.w(TAG, "Unsupported credential type " + mCredentialType + " for user " + mUserId);
+            // Keys will be synced when user starts using non custom screen lock.
+            if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED
+                    || mUserId != UserHandle.USER_SYSTEM) {
+                mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId);
+            }
+            return;
+        }
+        if (mPlatformKeyManager.isDeviceLocked(mUserId) && mUserId == UserHandle.USER_SYSTEM) {
+            Log.w(TAG, "Can't sync keys for locked user " + mUserId);
             return;
         }
 
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 0ad6c2a..0761cde 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -67,8 +67,9 @@
  * @hide
  */
 public class PlatformKeyManager {
-    private static final String TAG = "PlatformKeyManager";
+    static final int MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED = 1000000;
 
+    private static final String TAG = "PlatformKeyManager";
     private static final String KEY_ALGORITHM = "AES";
     private static final int KEY_SIZE_BITS = 256;
     private static final String KEY_ALIAS_PREFIX =
@@ -131,14 +132,14 @@
 
     /**
      * Returns {@code true} if the platform key is available. A platform key won't be available if
-     * the user has not set up a lock screen.
+     * device is locked.
      *
      * @param userId The ID of the user to whose lock screen the platform key must be bound.
      *
      * @hide
      */
-    public boolean isAvailable(int userId) {
-        return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId);
+    public boolean isDeviceLocked(int userId) {
+        return mContext.getSystemService(KeyguardManager.class).isDeviceLocked(userId);
     }
 
     /**
@@ -169,7 +170,6 @@
      * @param userId The ID of the user to whose lock screen the platform key must be bound.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
      * @throws KeyStoreException if there is an error in AndroidKeyStore.
-     * @throws InsecureUserException if the user does not have a lock screen set.
      * @throws IOException if there was an issue with local database update.
      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
      *
@@ -177,13 +177,8 @@
      */
     @VisibleForTesting
     void regenerate(int userId)
-            throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException,
+            throws NoSuchAlgorithmException, KeyStoreException, IOException,
                     RemoteException {
-        if (!isAvailable(userId)) {
-            throw new InsecureUserException(String.format(
-                    Locale.US, "%d does not have a lock screen set.", userId));
-        }
-
         int generationId = getGenerationId(userId);
         int nextId;
         if (generationId == -1) {
@@ -192,6 +187,7 @@
             invalidatePlatformKey(userId, generationId);
             nextId = generationId + 1;
         }
+        generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED);
         generateAndLoadKey(userId, nextId);
     }
 
@@ -203,7 +199,6 @@
      * @throws KeyStoreException if there was an AndroidKeyStore error.
      * @throws UnrecoverableKeyException if the key could not be recovered.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
-     * @throws InsecureUserException if the user does not have a lock screen set.
      * @throws IOException if there was an issue with local database update.
      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
      *
@@ -211,7 +206,7 @@
      */
     public PlatformEncryptionKey getEncryptKey(int userId)
             throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
-                    InsecureUserException, IOException, RemoteException {
+                    IOException, RemoteException {
         init(userId);
         try {
             // Try to see if the decryption key is still accessible before using the encryption key.
@@ -234,12 +229,11 @@
      * @throws KeyStoreException if there was an AndroidKeyStore error.
      * @throws UnrecoverableKeyException if the key could not be recovered.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
-     * @throws InsecureUserException if the user does not have a lock screen set.
      *
      * @hide
      */
     private PlatformEncryptionKey getEncryptKeyInternal(int userId) throws KeyStoreException,
-           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
+            UnrecoverableKeyException, NoSuchAlgorithmException {
         int generationId = getGenerationId(userId);
         String alias = getEncryptAlias(userId, generationId);
         if (!isKeyLoaded(userId, generationId)) {
@@ -258,7 +252,6 @@
      * @throws KeyStoreException if there was an AndroidKeyStore error.
      * @throws UnrecoverableKeyException if the key could not be recovered.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
-     * @throws InsecureUserException if the user does not have a lock screen set.
      * @throws IOException if there was an issue with local database update.
      * @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
      *
@@ -266,7 +259,7 @@
      */
     public PlatformDecryptionKey getDecryptKey(int userId)
             throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
-                    InsecureUserException, IOException, RemoteException {
+                    IOException, RemoteException {
         init(userId);
         try {
             PlatformDecryptionKey decryptionKey = getDecryptKeyInternal(userId);
@@ -288,12 +281,11 @@
      * @throws KeyStoreException if there was an AndroidKeyStore error.
      * @throws UnrecoverableKeyException if the key could not be recovered.
      * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
-     * @throws InsecureUserException if the user does not have a lock screen set.
      *
      * @hide
      */
     private PlatformDecryptionKey getDecryptKeyInternal(int userId) throws KeyStoreException,
-           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
+            UnrecoverableKeyException, NoSuchAlgorithmException {
         int generationId = getGenerationId(userId);
         String alias = getDecryptAlias(userId, generationId);
         if (!isKeyLoaded(userId, generationId)) {
@@ -340,13 +332,8 @@
      * @hide
      */
     void init(int userId)
-            throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException,
+            throws KeyStoreException, NoSuchAlgorithmException, IOException,
                     RemoteException {
-        if (!isAvailable(userId)) {
-            throw new InsecureUserException(String.format(
-                    Locale.US, "%d does not have a lock screen set.", userId));
-        }
-
         int generationId = getGenerationId(userId);
         if (isKeyLoaded(userId, generationId)) {
             Log.i(TAG, String.format(
@@ -363,6 +350,7 @@
             generationId++;
         }
 
+        generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED);
         generateAndLoadKey(userId, generationId);
     }
 
@@ -440,12 +428,16 @@
 
         KeyProtection.Builder decryptionKeyProtection =
                 new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
-                    .setUserAuthenticationRequired(true)
-                    .setUserAuthenticationValidityDurationSeconds(
-                            USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS)
                     .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE);
-        if (userId != UserHandle.USER_SYSTEM) {
+        // Skip UserAuthenticationRequired for main user
+        if (userId ==  UserHandle.USER_SYSTEM) {
+            decryptionKeyProtection.setUnlockedDeviceRequired(true);
+        } else {
+            // With setUnlockedDeviceRequired, KeyStore thinks that device is locked .
+            decryptionKeyProtection.setUserAuthenticationRequired(true);
+            decryptionKeyProtection.setUserAuthenticationValidityDurationSeconds(
+                            USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS);
             // Bind decryption key to secondary profile lock screen secret.
             long secureUserId = getGateKeeperService().getSecureUserId(userId);
             // TODO(b/124095438): Propagate this failure instead of silently failing.
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 383d5cf..6d97ed7 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -19,7 +19,6 @@
 import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT;
 import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED;
 import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE;
-import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER;
 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE;
 import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT;
 import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
@@ -46,7 +45,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.HexDump;
-import com.android.internal.util.Preconditions;
 import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException;
 import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils;
 import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException;
@@ -76,8 +74,9 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
-import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 
 import javax.crypto.AEADBadTagException;
 
@@ -89,13 +88,14 @@
  */
 public class RecoverableKeyStoreManager {
     private static final String TAG = "RecoverableKeyStoreMgr";
+    private static final long SYNC_DELAY_MILLIS = 2000;
 
     private static RecoverableKeyStoreManager mInstance;
 
     private final Context mContext;
     private final RecoverableKeyStoreDb mDatabase;
     private final RecoverySessionStorage mRecoverySessionStorage;
-    private final ExecutorService mExecutorService;
+    private final ScheduledExecutorService mExecutorService;
     private final RecoverySnapshotListenersStorage mListenersStorage;
     private final RecoverableKeyGenerator mRecoverableKeyGenerator;
     private final RecoverySnapshotStorage mSnapshotStorage;
@@ -136,7 +136,7 @@
                     context.getApplicationContext(),
                     db,
                     new RecoverySessionStorage(),
-                    Executors.newSingleThreadExecutor(),
+                    Executors.newScheduledThreadPool(1),
                     snapshotStorage,
                     new RecoverySnapshotListenersStorage(),
                     platformKeyManager,
@@ -152,7 +152,7 @@
             Context context,
             RecoverableKeyStoreDb recoverableKeyStoreDb,
             RecoverySessionStorage recoverySessionStorage,
-            ExecutorService executorService,
+            ScheduledExecutorService executorService,
             RecoverySnapshotStorage snapshotStorage,
             RecoverySnapshotListenersStorage listenersStorage,
             PlatformKeyManager platformKeyManager,
@@ -724,8 +724,6 @@
             throw new RuntimeException(e);
         } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
-        } catch (InsecureUserException e) {
-            throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
         }
 
         try {
@@ -793,8 +791,6 @@
             throw new RuntimeException(e);
         } catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
-        } catch (InsecureUserException e) {
-            throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
         }
 
         try {
@@ -915,7 +911,7 @@
             int storedHashType, @NonNull byte[] credential, int userId) {
         // So as not to block the critical path unlocking the phone, defer to another thread.
         try {
-            mExecutorService.execute(KeySyncTask.newInstance(
+            mExecutorService.schedule(KeySyncTask.newInstance(
                     mContext,
                     mDatabase,
                     mSnapshotStorage,
@@ -923,7 +919,10 @@
                     userId,
                     storedHashType,
                     credential,
-                    /*credentialUpdated=*/ false));
+                    /*credentialUpdated=*/ false),
+                    SYNC_DELAY_MILLIS,
+                    TimeUnit.MILLISECONDS
+            );
         } catch (NoSuchAlgorithmException e) {
             Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
         } catch (KeyStoreException e) {
@@ -947,7 +946,7 @@
             int userId) {
         // So as not to block the critical path unlocking the phone, defer to another thread.
         try {
-            mExecutorService.execute(KeySyncTask.newInstance(
+            mExecutorService.schedule(KeySyncTask.newInstance(
                     mContext,
                     mDatabase,
                     mSnapshotStorage,
@@ -955,7 +954,10 @@
                     userId,
                     storedHashType,
                     credential,
-                    /*credentialUpdated=*/ true));
+                    /*credentialUpdated=*/ true),
+                    SYNC_DELAY_MILLIS,
+                    TimeUnit.MILLISECONDS
+            );
         } catch (NoSuchAlgorithmException e) {
             Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
         } catch (KeyStoreException e) {
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
new file mode 100644
index 0000000..5191833
--- /dev/null
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.MediaRoute2Info;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+class BluetoothRouteProvider {
+    private static final String TAG = "BTRouteProvider";
+    private static BluetoothRouteProvider sInstance;
+
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    BluetoothA2dp mA2dpProfile;
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    BluetoothHearingAid mHearingAidProfile;
+
+    private final Context mContext;
+    private final BluetoothAdapter mBluetoothAdapter;
+    private final BluetoothRoutesUpdatedListener mListener;
+    private final Map<String, BluetoothEventReceiver> mEventReceiverMap = new HashMap<>();
+    private final IntentFilter mIntentFilter = new IntentFilter();
+    private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
+    private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
+
+    private BluetoothDevice mActiveDevice = null;
+
+    static synchronized BluetoothRouteProvider getInstance(@NonNull Context context,
+            @NonNull BluetoothRoutesUpdatedListener listener) {
+        Objects.requireNonNull(context);
+        Objects.requireNonNull(listener);
+
+        if (sInstance == null) {
+            BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+            if (btAdapter == null) {
+                return null;
+            }
+            sInstance = new BluetoothRouteProvider(context, btAdapter, listener);
+        }
+        return sInstance;
+    }
+
+    private BluetoothRouteProvider(Context context, BluetoothAdapter btAdapter,
+            BluetoothRoutesUpdatedListener listener) {
+        mContext = context;
+        mBluetoothAdapter = btAdapter;
+        mListener = listener;
+        buildBluetoothRoutes();
+
+        mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
+        mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
+
+        // Bluetooth on/off broadcasts
+        addEventReceiver(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedReceiver());
+
+        // Pairing broadcasts
+        addEventReceiver(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedReceiver());
+
+        DeviceStateChangedRecevier deviceStateChangedReceiver = new DeviceStateChangedRecevier();
+        addEventReceiver(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, deviceStateChangedReceiver);
+        addEventReceiver(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, deviceStateChangedReceiver);
+        addEventReceiver(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
+                deviceStateChangedReceiver);
+        addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED,
+                deviceStateChangedReceiver);
+
+        mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, null);
+    }
+
+    private void addEventReceiver(String action, BluetoothEventReceiver eventReceiver) {
+        mEventReceiverMap.put(action, eventReceiver);
+        mIntentFilter.addAction(action);
+    }
+
+    private void buildBluetoothRoutes() {
+        mBluetoothRoutes.clear();
+        for (BluetoothDevice device : mBluetoothAdapter.getBondedDevices()) {
+            if (device.isConnected()) {
+                BluetoothRouteInfo newBtRoute = createBluetoothRoute(device);
+                mBluetoothRoutes.put(device.getAddress(), newBtRoute);
+            }
+        }
+    }
+
+    @NonNull List<MediaRoute2Info> getBluetoothRoutes() {
+        ArrayList<MediaRoute2Info> routes = new ArrayList<>();
+        for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
+            routes.add(btRoute.route);
+        }
+        return routes;
+    }
+
+    private void notifyBluetoothRoutesUpdated() {
+        if (mListener != null) {
+            mListener.onBluetoothRoutesUpdated(getBluetoothRoutes());
+        }
+    }
+
+    private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
+        BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo();
+        newBtRoute.btDevice = device;
+        newBtRoute.route = new MediaRoute2Info.Builder(device.getAddress(), device.getName())
+                .addFeature(SystemMediaRoute2Provider.TYPE_LIVE_AUDIO)
+                .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
+                .setDescription(mContext.getResources().getText(
+                        R.string.bluetooth_a2dp_audio_route_name).toString())
+                .build();
+        newBtRoute.connectedProfiles = new SparseBooleanArray();
+        return newBtRoute;
+    }
+
+    private void setRouteConnectionStateForDevice(BluetoothDevice device,
+            @MediaRoute2Info.ConnectionState int state) {
+        if (device == null) {
+            Log.w(TAG, "setRouteConnectionStateForDevice: device shouldn't be null");
+            return;
+        }
+        BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+        if (btRoute == null) {
+            Log.w(TAG, "setRouteConnectionStateForDevice: route shouldn't be null");
+            return;
+        }
+        if (btRoute.route.getConnectionState() != state) {
+            btRoute.route = new MediaRoute2Info.Builder(btRoute.route)
+                    .setConnectionState(state).build();
+        }
+    }
+
+    interface BluetoothRoutesUpdatedListener {
+        void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes);
+    }
+
+    private class BluetoothRouteInfo {
+        public BluetoothDevice btDevice;
+        public MediaRoute2Info route;
+        public SparseBooleanArray connectedProfiles;
+    }
+
+    // These callbacks run on the main thread.
+    private final class BluetoothProfileListener implements BluetoothProfile.ServiceListener {
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            switch (profile) {
+                case BluetoothProfile.A2DP:
+                    mA2dpProfile = (BluetoothA2dp) proxy;
+                    break;
+                case BluetoothProfile.HEARING_AID:
+                    mHearingAidProfile = (BluetoothHearingAid) proxy;
+                    break;
+                default:
+                    return;
+            }
+            for (BluetoothDevice device : proxy.getConnectedDevices()) {
+                BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+                if (btRoute == null) {
+                    btRoute = createBluetoothRoute(device);
+                    mBluetoothRoutes.put(device.getAddress(), btRoute);
+                }
+                btRoute.connectedProfiles.put(profile, true);
+            }
+        }
+
+        public void onServiceDisconnected(int profile) {
+            switch (profile) {
+                case BluetoothProfile.A2DP:
+                    mA2dpProfile = null;
+                    break;
+                case BluetoothProfile.HEARING_AID:
+                    mHearingAidProfile = null;
+                    break;
+                default:
+                    return;
+            }
+        }
+    }
+    private class BluetoothBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+            BluetoothEventReceiver receiver = mEventReceiverMap.get(action);
+            if (receiver != null) {
+                receiver.onReceive(context, intent, device);
+            }
+        }
+    }
+
+    private interface BluetoothEventReceiver {
+        void onReceive(Context context, Intent intent, BluetoothDevice device);
+    }
+
+    private class AdapterStateChangedReceiver implements BluetoothEventReceiver {
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
+            if (state == BluetoothAdapter.STATE_OFF
+                    || state == BluetoothAdapter.STATE_TURNING_OFF) {
+                mBluetoothRoutes.clear();
+                notifyBluetoothRoutesUpdated();
+            } else if (state == BluetoothAdapter.STATE_ON) {
+                buildBluetoothRoutes();
+                if (!mBluetoothRoutes.isEmpty()) {
+                    notifyBluetoothRoutesUpdated();
+                }
+            }
+        }
+    }
+
+    private class BondStateChangedReceiver implements BluetoothEventReceiver {
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+                    BluetoothDevice.ERROR);
+            BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+            if (bondState == BluetoothDevice.BOND_BONDED && btRoute == null) {
+                btRoute = createBluetoothRoute(device);
+                if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) {
+                    btRoute.connectedProfiles.put(BluetoothProfile.A2DP, true);
+                }
+                if (mHearingAidProfile != null
+                        && mHearingAidProfile.getConnectedDevices().contains(device)) {
+                    btRoute.connectedProfiles.put(BluetoothProfile.HEARING_AID, true);
+                }
+                mBluetoothRoutes.put(device.getAddress(), btRoute);
+                notifyBluetoothRoutesUpdated();
+            } else if (bondState == BluetoothDevice.BOND_NONE
+                    && mBluetoothRoutes.remove(device.getAddress()) != null) {
+                notifyBluetoothRoutesUpdated();
+            }
+        }
+    }
+
+    private class DeviceStateChangedRecevier implements BluetoothEventReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+            switch (intent.getAction()) {
+                case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
+                    String prevActiveDeviceAddress =
+                            (mActiveDevice == null) ? null : mActiveDevice.getAddress();
+                    String curActiveDeviceAddress =
+                            (device == null) ? null : device.getAddress();
+                    if (!TextUtils.equals(prevActiveDeviceAddress, curActiveDeviceAddress)) {
+                        if (mActiveDevice != null) {
+                            setRouteConnectionStateForDevice(mActiveDevice,
+                                    MediaRoute2Info.CONNECTION_STATE_DISCONNECTED);
+                        }
+                        if (device != null) {
+                            setRouteConnectionStateForDevice(device,
+                                    MediaRoute2Info.CONNECTION_STATE_CONNECTED);
+                        }
+                        notifyBluetoothRoutesUpdated();
+                        mActiveDevice = device;
+                    }
+                    break;
+                case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
+                    handleConnectionStateChanged(BluetoothProfile.A2DP, intent, device);
+                    break;
+            }
+        }
+
+        private void handleConnectionStateChanged(int profile, Intent intent,
+                BluetoothDevice device) {
+            int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+            BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+            if (state == BluetoothProfile.STATE_CONNECTED) {
+                if (btRoute == null) {
+                    btRoute = createBluetoothRoute(device);
+                    mBluetoothRoutes.put(device.getAddress(), btRoute);
+                    btRoute.connectedProfiles.put(profile, true);
+                    notifyBluetoothRoutesUpdated();
+                } else {
+                    btRoute.connectedProfiles.put(profile, true);
+                }
+            } else if (state == BluetoothProfile.STATE_DISCONNECTING
+                    || state == BluetoothProfile.STATE_DISCONNECTED) {
+                btRoute.connectedProfiles.delete(profile);
+                if (btRoute.connectedProfiles.size() == 0) {
+                    mBluetoothRoutes.remove(device.getAddress());
+                    if (mActiveDevice != null
+                            && TextUtils.equals(mActiveDevice.getAddress(), device.getAddress())) {
+                        mActiveDevice = null;
+                    }
+                    notifyBluetoothRoutesUpdated();
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 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/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index d66fd57..da2ca9b 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -43,7 +43,8 @@
         }
 
         record.updateNotificationChannel(mConfig.getNotificationChannel(record.sbn.getPackageName(),
-                record.sbn.getUid(), record.getChannel().getId(), false));
+                record.sbn.getUid(), record.getChannel().getId(),
+                record.getNotification().getShortcutId(), false));
 
         return null;
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 728b297..0e08033 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -25,6 +25,7 @@
 import static android.app.Notification.FLAG_NO_CLEAR;
 import static android.app.Notification.FLAG_ONGOING_EVENT;
 import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
+import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
 import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
 import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
@@ -236,6 +237,7 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.TriPredicate;
 import com.android.server.DeviceIdleInternal;
@@ -1008,12 +1010,14 @@
             if (clearEffects) {
                 clearEffects();
             }
+            mAssistants.onPanelRevealed(items);
         }
 
         @Override
         public void onPanelHidden() {
             MetricsLogger.hidden(getContext(), MetricsEvent.NOTIFICATION_PANEL);
             EventLogTags.writeNotificationPanelHidden();
+            mAssistants.onPanelHidden();
         }
 
         @Override
@@ -1060,6 +1064,7 @@
                         reportSeen(r);
                     }
                     r.setVisibility(true, nv.rank, nv.count);
+                    mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, true);
                     boolean isHun = (nv.location
                             == NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
                     // hasBeenVisiblyExpanded must be called after updating the expansion state of
@@ -1078,6 +1083,7 @@
                     NotificationRecord r = mNotificationsByKey.get(nv.key);
                     if (r == null) continue;
                     r.setVisibility(false, nv.rank, nv.count);
+                    mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, false);
                     nv.recycle();
                 }
             }
@@ -2219,8 +2225,8 @@
         maybeNotifyChannelOwner(pkg, uid, preUpdate, channel);
 
         if (!fromListener) {
-            final NotificationChannel modifiedChannel =
-                    mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+            final NotificationChannel modifiedChannel = mPreferencesHelper.getNotificationChannel(
+                    pkg, uid, channel.getId(), false);
             mListeners.notifyNotificationChannelChanged(
                     pkg, UserHandle.getUserHandleForUid(uid),
                     modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
@@ -3017,21 +3023,43 @@
 
         @Override
         public void createNotificationChannels(String pkg,
-                ParceledListSlice channelsList) throws RemoteException {
+                ParceledListSlice channelsList) {
             checkCallerIsSystemOrSameApp(pkg);
             createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList);
         }
 
         @Override
         public void createNotificationChannelsForPackage(String pkg, int uid,
-                ParceledListSlice channelsList) throws RemoteException {
-            checkCallerIsSystem();
+                ParceledListSlice channelsList) {
+            enforceSystemOrSystemUI("only system can call this");
             createNotificationChannelsImpl(pkg, uid, channelsList);
         }
 
         @Override
+        public void createConversationNotificationChannelForPackage(String pkg, int uid,
+                NotificationChannel parentChannel, String conversationId) {
+            enforceSystemOrSystemUI("only system can call this");
+            Preconditions.checkNotNull(parentChannel);
+            Preconditions.checkNotNull(conversationId);
+            String parentId = parentChannel.getId();
+            NotificationChannel conversationChannel = parentChannel;
+            conversationChannel.setId(String.format(
+                    CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId));
+            conversationChannel.setConversationId(parentId, conversationId);
+            createNotificationChannelsImpl(
+                    pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)));
+        }
+
+        @Override
         public NotificationChannel getNotificationChannel(String callingPkg, int userId,
                 String targetPkg, String channelId) {
+            return getConversationNotificationChannel(
+                    callingPkg, userId, targetPkg, channelId, null);
+        }
+
+        @Override
+        public NotificationChannel getConversationNotificationChannel(String callingPkg, int userId,
+                String targetPkg, String channelId, String conversationId) {
             if (canNotifyAsPackage(callingPkg, targetPkg, userId)
                     || isCallingUidSystem()) {
                 int targetUid = -1;
@@ -3041,7 +3069,8 @@
                     /* ignore */
                 }
                 return mPreferencesHelper.getNotificationChannel(
-                        targetPkg, targetUid, channelId, false /* includeDeleted */);
+                        targetPkg, targetUid, channelId, conversationId,
+                        false /* includeDeleted */);
             }
             throw new SecurityException("Pkg " + callingPkg
                     + " cannot read channels for " + targetPkg + " in " + userId);
@@ -3072,6 +3101,30 @@
         }
 
         @Override
+        public void deleteConversationNotificationChannels(String pkg, int uid,
+                String conversationId) {
+            checkCallerIsSystem();
+            final int callingUid = Binder.getCallingUid();
+            List<NotificationChannel> channels =
+                    mPreferencesHelper.getNotificationChannelsByConversationId(
+                            pkg, uid, conversationId);
+            if (!channels.isEmpty()) {
+                for (NotificationChannel nc : channels) {
+                    cancelAllNotificationsInt(MY_UID, MY_PID, pkg, nc.getId(), 0, 0, true,
+                            UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null);
+                    mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, nc.getId());
+                    mListeners.notifyNotificationChannelChanged(pkg,
+                            UserHandle.getUserHandleForUid(callingUid),
+                            mPreferencesHelper.getNotificationChannel(
+                                    pkg, callingUid, nc.getId(), true),
+                            NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
+                }
+                handleSavePolicyFile();
+            }
+        }
+
+
+        @Override
         public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) {
             checkCallerIsSystemOrSameApp(pkg);
             return mPreferencesHelper.getNotificationChannelGroupWithChannels(
@@ -5203,7 +5256,8 @@
             channelId = (new Notification.TvExtender(notification)).getChannelId();
         }
         final NotificationChannel channel = mPreferencesHelper.getNotificationChannel(pkg,
-                notificationUid, channelId, false /* includeDeleted */);
+                notificationUid, channelId, notification.getShortcutId(),
+                false /* includeDeleted */);
         if (channel == null) {
             final String noChannelStr = "No Channel found for "
                     + "pkg=" + pkg
@@ -8296,6 +8350,32 @@
             }
         }
 
+        protected void onPanelRevealed(int items) {
+            for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
+                mHandler.post(() -> {
+                    final INotificationListener assistant = (INotificationListener) info.service;
+                    try {
+                        assistant.onPanelRevealed(items);
+                    } catch (RemoteException ex) {
+                        Slog.e(TAG, "unable to notify assistant (panel revealed): " + info, ex);
+                    }
+                });
+            }
+        }
+
+        protected void onPanelHidden() {
+            for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
+                mHandler.post(() -> {
+                    final INotificationListener assistant = (INotificationListener) info.service;
+                    try {
+                        assistant.onPanelHidden();
+                    } catch (RemoteException ex) {
+                        Slog.e(TAG, "unable to notify assistant (panel hidden): " + info, ex);
+                    }
+                });
+            }
+        }
+
         boolean hasUserSet(int userId) {
             synchronized (mLock) {
                 return mUserSetMap.getOrDefault(userId, false);
@@ -8363,6 +8443,24 @@
         }
 
         @GuardedBy("mNotificationLock")
+        void notifyAssistantVisibilityChangedLocked(
+                final StatusBarNotification sbn,
+                final boolean isVisible) {
+            final String key = sbn.getKey();
+            Slog.d(TAG, "notifyAssistantVisibilityChangedLocked: " + key);
+            notifyAssistantLocked(
+                    sbn,
+                    false /* sameUserOnly */,
+                    (assistant, sbnHolder) -> {
+                        try {
+                            assistant.onNotificationVisibilityChanged(key, isVisible);
+                        } catch (RemoteException ex) {
+                            Slog.e(TAG, "unable to notify assistant (visible): " + assistant, ex);
+                        }
+                    });
+        }
+
+        @GuardedBy("mNotificationLock")
         void notifyAssistantExpansionChangedLocked(
                 final StatusBarNotification sbn,
                 final boolean isUserAction,
diff --git a/services/core/java/com/android/server/notification/OWNERS b/services/core/java/com/android/server/notification/OWNERS
new file mode 100644
index 0000000..5a19656
--- /dev/null
+++ b/services/core/java/com/android/server/notification/OWNERS
@@ -0,0 +1,4 @@
+dsandler@android.com
+juliacr@google.com
+beverlyt@google.com
+pixel@google.com
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index cdb0a17..92fcb7f 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -770,6 +770,13 @@
                 channel.setShowBadge(false);
             }
             channel.setOriginalImportance(channel.getImportance());
+
+            // validate parent
+            if (channel.getParentChannelId() != null) {
+                Preconditions.checkArgument(r.channels.containsKey(channel.getParentChannelId()),
+                        "Tried to create a conversation channel without a preexisting parent");
+            }
+
             r.channels.put(channel.getId(), channel);
             if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
                 updateChannelsBypassingDnd(mContext.getUserId());
@@ -851,6 +858,13 @@
     public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
             boolean includeDeleted) {
         Objects.requireNonNull(pkg);
+        return getNotificationChannel(pkg, uid, channelId, null, includeDeleted);
+    }
+
+    @Override
+    public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
+            String conversationId, boolean includeDeleted) {
+        Preconditions.checkNotNull(pkg);
         synchronized (mPackagePreferences) {
             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
             if (r == null) {
@@ -859,14 +873,51 @@
             if (channelId == null) {
                 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
             }
-            final NotificationChannel nc = r.channels.get(channelId);
-            if (nc != null && (includeDeleted || !nc.isDeleted())) {
-                return nc;
+            if (conversationId == null) {
+                final NotificationChannel nc = r.channels.get(channelId);
+                if (nc != null && (includeDeleted || !nc.isDeleted())) {
+                    return nc;
+                }
+            } else {
+                // look for an automatically created conversation specific channel
+                return findConversationChannel(r, channelId, conversationId, includeDeleted);
             }
             return null;
         }
     }
 
+    private NotificationChannel findConversationChannel(PackagePreferences p, String parentId,
+            String conversationId, boolean includeDeleted) {
+        for (NotificationChannel nc : p.channels.values()) {
+            if (conversationId.equals(nc.getConversationId())
+                    && parentId.equals(nc.getParentChannelId())
+                    && (includeDeleted || !nc.isDeleted())) {
+                return nc;
+            }
+        }
+        return null;
+    }
+
+    public List<NotificationChannel> getNotificationChannelsByConversationId(String pkg, int uid,
+            String conversationId) {
+        Preconditions.checkNotNull(pkg);
+        Preconditions.checkNotNull(conversationId);
+        List<NotificationChannel> channels = new ArrayList<>();
+        synchronized (mPackagePreferences) {
+            PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
+            if (r == null) {
+                return channels;
+            }
+            for (NotificationChannel nc : r.channels.values()) {
+                if (conversationId.equals(nc.getConversationId())
+                        && !nc.isDeleted()) {
+                    channels.add(nc);
+                }
+            }
+            return channels;
+        }
+    }
+
     @Override
     public void deleteNotificationChannel(String pkg, int uid, String channelId) {
         synchronized (mPackagePreferences) {
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 7816f36..4b044c1 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -41,10 +41,15 @@
             int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty);
     boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel,
             boolean fromTargetApp, boolean hasDndAccess);
-    void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser);
-    NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
+    void updateNotificationChannel(String pkg, int uid, NotificationChannel channel,
+            boolean fromUser);
+    NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
+            boolean includeDeleted);
+    NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
+            String conversationId, boolean includeDeleted);
     void deleteNotificationChannel(String pkg, int uid, String channelId);
     void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
     void permanentlyDeleteNotificationChannels(String pkg, int uid);
-    ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, boolean includeDeleted);
+    ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
+            boolean includeDeleted);
 }
diff --git a/services/core/java/com/android/server/om/TEST_MAPPING b/services/core/java/com/android/server/om/TEST_MAPPING
new file mode 100644
index 0000000..52163a0
--- /dev/null
+++ b/services/core/java/com/android/server/om/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.om."
+        }
+      ]
+    }
+  ]
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 97a2d43..165bdeb 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2413,16 +2413,6 @@
 
     @Override
     public void addFile(String name, long lengthBytes, byte[] metadata) {
-        if (mIncrementalFileStorages != null) {
-            try {
-                mIncrementalFileStorages.addFile(new InstallationFile(name, lengthBytes, metadata));
-                //TODO(b/136132412): merge incremental and callback installation schemes
-                return;
-            } catch (IOException ex) {
-                throw new IllegalStateException(
-                        "Failed to add and configure Incremental File: " + name, ex);
-            }
-        }
         if (!isDataLoaderInstallation()) {
             throw new IllegalStateException(
                     "Cannot add files to non-data loader installation session.");
@@ -2435,7 +2425,6 @@
         synchronized (mLock) {
             assertCallerIsOwnerOrRootLocked();
             assertPreparedAndNotSealedLocked("addFile");
-
             mFiles.add(FileInfo.added(name, lengthBytes, metadata));
         }
     }
@@ -2486,7 +2475,7 @@
      */
     private void prepareDataLoader()
             throws PackageManagerException, StreamingException {
-        if (!isStreamingInstallation()) {
+        if (!isDataLoaderInstallation()) {
             return;
         }
 
@@ -2500,6 +2489,18 @@
                     file -> file.name.substring(
                             0, file.name.length() - REMOVE_MARKER_EXTENSION.length())).collect(
                 Collectors.toList());
+        if (mIncrementalFileStorages != null) {
+            for (InstallationFile file : addedFiles) {
+                try {
+                    mIncrementalFileStorages.addFile(file);
+                } catch (IOException ex) {
+                    // TODO(b/146080380): add incremental-specific error code
+                    throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+                            "Failed to add and configure Incremental File: " + file.getName(), ex);
+                }
+            }
+            return;
+        }
 
         final FileSystemConnector connector = new FileSystemConnector(addedFiles);
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 52e8e37..17870eb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5348,8 +5348,9 @@
         if (!mUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForComponent(flags, userId);
-        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
-                false /* requireFullPermission */, false /* checkShell */, "get service info");
+        mPermissionManager.enforceCrossUserOrProfilePermission(
+                callingUid, userId, false /* requireFullPermission */, false /* checkShell */,
+                "get service info");
         synchronized (mLock) {
             ParsedService s = mComponentResolver.getService(component);
             if (DEBUG_PACKAGE_INFO) Log.v(
@@ -7799,8 +7800,10 @@
             String resolvedType, int flags, int userId, int callingUid,
             boolean includeInstantApps) {
         if (!mUserManager.exists(userId)) return Collections.emptyList();
-        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
-                false /*requireFullPermission*/, false /*checkShell*/,
+        mPermissionManager.enforceCrossUserOrProfilePermission(callingUid,
+                userId,
+                false /*requireFullPermission*/,
+                false /*checkShell*/,
                 "query intent receivers");
         final String instantAppPkgName = getInstantAppPackageName(callingUid);
         flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps);
@@ -19387,7 +19390,7 @@
             // PermissionController manages default home directly.
             return false;
         }
-        mPermissionManager.setDefaultHome(currentPackageName, userId, (successful) -> {
+        mPermissionManager.setDefaultHome(packageName, userId, (successful) -> {
             if (successful) {
                 postPreferredActivityChangedBroadcast(userId);
             }
@@ -20129,8 +20132,7 @@
         // Disable any carrier apps. We do this very early in boot to prevent the apps from being
         // disabled after already being started.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), this,
-                mPermissionManagerService, mContext.getContentResolver(),
-                UserHandle.USER_SYSTEM);
+                mPermissionManagerService, UserHandle.USER_SYSTEM, mContext);
 
         disableSkuSpecificApps();
 
@@ -22798,7 +22800,7 @@
             ArrayList<String> systemPackageNames = new ArrayList<>(pkgNames.length);
 
             for (String pkgName: pkgNames) {
-                synchronized (mPackages) {
+                synchronized (mLock) {
                     if (pkgName == null) {
                         continue;
                     }
@@ -23644,7 +23646,7 @@
 
     @Nullable
     public PackageSetting getPackageSetting(String packageName) {
-        synchronized (mPackages) {
+        synchronized (mLock) {
             packageName = resolveInternalPackageNameLPr(
                     packageName, PackageManager.VERSION_CODE_HIGHEST);
             return mSettings.mPackages.get(packageName);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index d921f31..d468cd9 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -4005,21 +4005,128 @@
             PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt,
                     UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
         }
-        if (!requirePermissionWhenSameUser && userId == UserHandle.getUserId(callingUid)) return;
-        if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) {
-            if (requireFullPermission) {
-                mContext.enforceCallingOrSelfPermission(
-                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
-            } else {
-                try {
-                    mContext.enforceCallingOrSelfPermission(
-                            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
-                } catch (SecurityException se) {
-                    mContext.enforceCallingOrSelfPermission(
-                            android.Manifest.permission.INTERACT_ACROSS_USERS, message);
-                }
-            }
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (hasCrossUserPermission(
+                callingUid, callingUserId, userId, requireFullPermission,
+                requirePermissionWhenSameUser)) {
+            return;
         }
+        String errorMessage = buildInvalidCrossUserPermissionMessage(
+                message, requireFullPermission);
+        Slog.w(TAG, errorMessage);
+        throw new SecurityException(errorMessage);
+    }
+
+    /**
+     * Checks if the request is from the system or an app that has the appropriate cross-user
+     * permissions defined as follows:
+     * <ul>
+     * <li>INTERACT_ACROSS_USERS_FULL if {@code requireFullPermission} is true.</li>
+     * <li>INTERACT_ACROSS_USERS if the given {@userId} is in a different profile group
+     * to the caller.</li>
+     * <li>Otherwise, INTERACT_ACROSS_PROFILES if the given {@userId} is in the same profile group
+     * as the caller.</li>
+     * </ul>
+     *
+     * @param checkShell whether to prevent shell from access if there's a debugging restriction
+     * @param message the message to log on security exception
+     */
+    private void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+            boolean requireFullPermission, boolean checkShell,
+            String message) {
+        if (userId < 0) {
+            throw new IllegalArgumentException("Invalid userId " + userId);
+        }
+        if (checkShell) {
+            PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt,
+                    UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
+        }
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission,
+                /*requirePermissionWhenSameUser= */ false)) {
+            return;
+        }
+        final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId);
+        if (isSameProfileGroup
+                && hasPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES)) {
+            return;
+        }
+        String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage(
+                message, requireFullPermission, isSameProfileGroup);
+        Slog.w(TAG, errorMessage);
+        throw new SecurityException(errorMessage);
+    }
+
+    private boolean hasCrossUserPermission(
+            int callingUid, int callingUserId, int userId, boolean requireFullPermission,
+            boolean requirePermissionWhenSameUser) {
+        if (!requirePermissionWhenSameUser && userId == callingUserId) {
+            return true;
+        }
+        if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) {
+            return true;
+        }
+        if (requireFullPermission) {
+            return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        }
+        return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS);
+    }
+
+    private boolean hasPermission(String permission) {
+        return mContext.checkCallingOrSelfPermission(permission)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return UserManagerService.getInstance().isSameProfileGroup(callerUserId, userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private static String buildInvalidCrossUserPermissionMessage(
+            String message, boolean requireFullPermission) {
+        StringBuilder builder = new StringBuilder();
+        if (message != null) {
+            builder.append(message);
+            builder.append(": ");
+        }
+        builder.append("Requires ");
+        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        if (requireFullPermission) {
+            builder.append(".");
+            return builder.toString();
+        }
+        builder.append(" or ");
+        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+        builder.append(".");
+        return builder.toString();
+    }
+
+    private static String buildInvalidCrossUserOrProfilePermissionMessage(
+            String message, boolean requireFullPermission, boolean isSameProfileGroup) {
+        StringBuilder builder = new StringBuilder();
+        if (message != null) {
+            builder.append(message);
+            builder.append(": ");
+        }
+        builder.append("Requires ");
+        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        if (requireFullPermission) {
+            builder.append(".");
+            return builder.toString();
+        }
+        builder.append(" or ");
+        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+        if (isSameProfileGroup) {
+            builder.append(" or ");
+            builder.append(android.Manifest.permission.INTERACT_ACROSS_PROFILES);
+        }
+        builder.append(".");
+        return builder.toString();
     }
 
     @GuardedBy({"mSettings.mLock", "mLock"})
@@ -4215,6 +4322,17 @@
             PermissionManagerService.this.enforceCrossUserPermission(callingUid, userId,
                     requireFullPermission, checkShell, requirePermissionWhenSameUser, message);
         }
+
+        @Override
+        public void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+                boolean requireFullPermission, boolean checkShell, String message) {
+            PermissionManagerService.this.enforceCrossUserOrProfilePermission(callingUid,
+                    userId,
+                    requireFullPermission,
+                    checkShell,
+                    message);
+        }
+
         @Override
         public void enforceGrantRevokeRuntimePermissionPermissions(String message) {
             PermissionManagerService.this.enforceGrantRevokeRuntimePermissionPermissions(message);
@@ -4453,8 +4571,8 @@
 
         @Override
         public void onNewUserCreated(int userId) {
+            mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId);
             synchronized (mLock) {
-                mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId);
                 // NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG
                 PermissionManagerService.this.updateAllPermissions(
                         StorageManager.UUID_PRIVATE_INTERNAL, true, mDefaultPermissionCallback);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 0f22619..58a9f42 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -271,6 +271,15 @@
      */
     public abstract void enforceCrossUserPermission(int callingUid, int userId,
             boolean requireFullPermission, boolean checkShell, @NonNull String message);
+
+    /**
+     * Similar to {@link #enforceCrossUserPermission(int, int, boolean, boolean, String)}
+     * but also allows INTERACT_ACROSS_PROFILES permission if calling user and {@code userId} are
+     * in the same profile group.
+     */
+    public abstract void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+            boolean requireFullPermission, boolean checkShell, @NonNull String message);
+
     /**
      * @see #enforceCrossUserPermission(int, int, boolean, boolean, String)
      * @param requirePermissionWhenSameUser When {@code true}, still require the cross user
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index 0b89646..a641f06 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -21,8 +21,10 @@
 import android.hardware.audio.common.V2_0.Uuid;
 import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
 import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
+import android.hardware.soundtrigger.V2_3.Properties;
 import android.media.audio.common.AudioConfig;
 import android.media.audio.common.AudioOffloadInfo;
+import android.media.soundtrigger_middleware.AudioCapabilities;
 import android.media.soundtrigger_middleware.ConfidenceLevel;
 import android.media.soundtrigger_middleware.ModelParameter;
 import android.media.soundtrigger_middleware.ModelParameterRange;
@@ -69,6 +71,15 @@
         return aidlProperties;
     }
 
+    static @NonNull SoundTriggerModuleProperties hidl2aidlProperties(
+            @NonNull Properties hidlProperties) {
+        SoundTriggerModuleProperties aidlProperties = hidl2aidlProperties(hidlProperties.base);
+        aidlProperties.supportedModelArch = hidlProperties.supportedModelArch;
+        aidlProperties.audioCapabilities =
+                hidl2aidlAudioCapabilities(hidlProperties.audioCapabilities);
+        return aidlProperties;
+    }
+
     static @NonNull
     String hidl2aidlUuid(@NonNull Uuid hidlUuid) {
         if (hidlUuid.node == null || hidlUuid.node.length != 6) {
@@ -201,16 +212,17 @@
         return hidlModel;
     }
 
-    static @NonNull
-    ISoundTriggerHw.RecognitionConfig aidl2hidlRecognitionConfig(
+    static @NonNull android.hardware.soundtrigger.V2_3.RecognitionConfig aidl2hidlRecognitionConfig(
             @NonNull RecognitionConfig aidlConfig) {
-        ISoundTriggerHw.RecognitionConfig hidlConfig = new ISoundTriggerHw.RecognitionConfig();
-        hidlConfig.header.captureRequested = aidlConfig.captureRequested;
+        android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
+                new android.hardware.soundtrigger.V2_3.RecognitionConfig();
+        hidlConfig.base.header.captureRequested = aidlConfig.captureRequested;
         for (PhraseRecognitionExtra aidlPhraseExtra : aidlConfig.phraseRecognitionExtras) {
-            hidlConfig.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
+            hidlConfig.base.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
         }
-        hidlConfig.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
+        hidlConfig.base.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
                 "SoundTrigger RecognitionConfig");
+        hidlConfig.audioCapabilities = aidlConfig.audioCapabilities;
         return hidlConfig;
     }
 
@@ -387,4 +399,17 @@
                 return android.hardware.soundtrigger.V2_3.ModelParameter.INVALID;
         }
     }
+
+    static int hidl2aidlAudioCapabilities(int hidlCapabilities) {
+        int aidlCapabilities = 0;
+        if ((hidlCapabilities
+                & android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION) != 0) {
+            aidlCapabilities |= AudioCapabilities.ECHO_CANCELLATION;
+        }
+        if ((hidlCapabilities
+                & android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION) != 0) {
+            aidlCapabilities |= AudioCapabilities.NOISE_SUPPRESSION;
+        }
+        return aidlCapabilities;
+    }
 }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
index f0a0d83..a42d292 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
@@ -66,12 +66,25 @@
         return model_2_0;
     }
 
-    static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_1_to_2_0(
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config) {
+    static android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_3_to_2_1(
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config) {
+        return config.base;
+    }
+
+    static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_3_to_2_0(
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config) {
         android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
-                config.header;
+                config.base.header;
         // Note: this mutates the input!
-        config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.data);
+        config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.base.data);
         return config_2_0;
     }
+
+    static android.hardware.soundtrigger.V2_3.Properties convertProperties_2_0_to_2_3(
+            android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties) {
+        android.hardware.soundtrigger.V2_3.Properties properties_2_3 =
+                new android.hardware.soundtrigger.V2_3.Properties();
+        properties_2_3.base = properties;
+        return properties_2_3;
+    }
 }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
index 81252c9..8b434bd 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
@@ -16,7 +16,6 @@
 
 package com.android.server.soundtrigger_middleware;
 
-import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
 import android.hardware.soundtrigger.V2_3.ModelParameterRange;
 import android.hidl.base.V1_0.IBase;
 import android.os.IHwBinder;
@@ -54,9 +53,10 @@
  */
 public interface ISoundTriggerHw2 {
     /**
-     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getProperties(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback
+     * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#getPropertiesEx(
+     * android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getPropertiesExCallback)
      */
-    android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties();
+    android.hardware.soundtrigger.V2_3.Properties getProperties();
 
     /**
      * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel,
@@ -92,12 +92,12 @@
     void stopAllRecognitions();
 
     /**
-     * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#startRecognition_2_1(int,
-     * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig,
+     * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#startRecognition_2_3(int,
+     * android.hardware.soundtrigger.V2_3.RecognitionConfig,
      * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int)
      */
     void startRecognition(int modelHandle,
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config,
             SoundTriggerHw2Compat.Callback callback, int cookie);
 
     /**
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
index 4a852c4..2f087f4 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -112,18 +112,23 @@
     }
 
     @Override
-    public android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties() {
+    public android.hardware.soundtrigger.V2_3.Properties getProperties() {
         try {
             AtomicInteger retval = new AtomicInteger(-1);
-            AtomicReference<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties>
+            AtomicReference<android.hardware.soundtrigger.V2_3.Properties>
                     properties =
                     new AtomicReference<>();
-            as2_0().getProperties(
-                    (r, p) -> {
-                        retval.set(r);
-                        properties.set(p);
-                    });
-            handleHalStatus(retval.get(), "getProperties");
+            try {
+                as2_3().getProperties_2_3(
+                        (r, p) -> {
+                            retval.set(r);
+                            properties.set(p);
+                        });
+            } catch (NotSupported e) {
+                // Fall-back to the 2.0 version:
+                return getProperties_2_0();
+            }
+            handleHalStatus(retval.get(), "getProperties_2_3");
             return properties.get();
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
@@ -212,16 +217,15 @@
 
     @Override
     public void startRecognition(int modelHandle,
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config,
             Callback callback, int cookie) {
         try {
             try {
-                int retval = as2_1().startRecognition_2_1(modelHandle, config,
-                        new SoundTriggerCallback(callback), cookie);
-                handleHalStatus(retval, "startRecognition_2_1");
+                int retval = as2_3().startRecognition_2_3(modelHandle, config);
+                handleHalStatus(retval, "startRecognition_2_3");
             } catch (NotSupported e) {
                 // Fall-back to the 2.0 version:
-                startRecognition_2_0(modelHandle, config, callback, cookie);
+                startRecognition_2_1(modelHandle, config, callback, cookie);
             }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
@@ -312,6 +316,21 @@
         return as2_0().interfaceDescriptor();
     }
 
+    private android.hardware.soundtrigger.V2_3.Properties getProperties_2_0()
+            throws RemoteException {
+        AtomicInteger retval = new AtomicInteger(-1);
+        AtomicReference<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties>
+                properties =
+                new AtomicReference<>();
+        as2_0().getProperties(
+                (r, p) -> {
+                    retval.set(r);
+                    properties.set(p);
+                });
+        handleHalStatus(retval.get(), "getProperties");
+        return Hw2CompatUtil.convertProperties_2_0_to_2_3(properties.get());
+    }
+
     private int loadSoundModel_2_0(
             android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
             Callback callback, int cookie)
@@ -349,13 +368,31 @@
         return handle.get();
     }
 
+    private void startRecognition_2_1(int modelHandle,
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config,
+            Callback callback, int cookie) {
+        try {
+            try {
+                android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config_2_1 =
+                        Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_1(config);
+                int retval = as2_1().startRecognition_2_1(modelHandle, config_2_1,
+                        new SoundTriggerCallback(callback), cookie);
+                handleHalStatus(retval, "startRecognition_2_1");
+            } catch (NotSupported e) {
+                // Fall-back to the 2.0 version:
+                startRecognition_2_0(modelHandle, config, callback, cookie);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
     private void startRecognition_2_0(int modelHandle,
-            android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+            android.hardware.soundtrigger.V2_3.RecognitionConfig config,
             Callback callback, int cookie)
             throws RemoteException {
-
         android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
-                Hw2CompatUtil.convertRecognitionConfig_2_1_to_2_0(config);
+                Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_0(config);
         int retval = as2_0().startRecognition(modelHandle, config_2_0,
                 new SoundTriggerCallback(callback), cookie);
         handleHalStatus(retval, "startRecognition");
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index 987c05f..5a06a2c 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.PermissionChecker;
 import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
@@ -32,6 +33,7 @@
 import android.media.soundtrigger_middleware.RecognitionStatus;
 import android.media.soundtrigger_middleware.SoundModel;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.Status;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.Log;
@@ -223,23 +225,48 @@
     }
 
     /**
-     * Throws a {@link SecurityException} if caller doesn't have the right permissions to use this
-     * service.
+     * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+     * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+     * caller temporarily doesn't have the right permissions to use this service.
      */
     private void checkPermissions() {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.RECORD_AUDIO,
-                "Caller must have the android.permission.RECORD_AUDIO permission.");
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD,
-                "Caller must have the android.permission.CAPTURE_AUDIO_HOTWORD permission.");
+        enforcePermission(Manifest.permission.RECORD_AUDIO);
+        enforcePermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD);
     }
 
     /**
-     * Throws a {@link SecurityException} if caller doesn't have the right permissions to preempt
-     * active sound trigger sessions.
+     * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+     * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+     * caller temporarily doesn't have the right permissions to preempt active sound trigger
+     * sessions.
      */
     private void checkPreemptPermissions() {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.PREEMPT_SOUND_TRIGGER,
-                "Caller must have the android.permission.PREEMPT_SOUND_TRIGGER permission.");
+        enforcePermission(Manifest.permission.PREEMPT_SOUND_TRIGGER);
+    }
+
+    /**
+     * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+     * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+     * caller temporarily doesn't have the given permission.
+     *
+     * @param permission The permission to check.
+     */
+    private void enforcePermission(String permission) {
+        final int status = PermissionChecker.checkCallingOrSelfPermissionForPreflight(mContext,
+                permission);
+        switch (status) {
+            case PermissionChecker.PERMISSION_GRANTED:
+                return;
+            case PermissionChecker.PERMISSION_DENIED:
+                throw new SecurityException(
+                        String.format("Caller must have the %s permission.", permission));
+            case PermissionChecker.PERMISSION_DENIED_APP_OP:
+                throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED,
+                        String.format("Caller must have the %s permission.", permission));
+            default:
+                throw new InternalServerError(
+                        new RuntimeException("Unexpected perimission check result."));
+        }
     }
 
     /** State of a sound model. */
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 81789e1..f024ede 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -401,10 +401,10 @@
                     notifyAbort();
                     return;
                 }
-                android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig hidlConfig =
+                android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
                         ConversionUtil.aidl2hidlRecognitionConfig(config);
-                hidlConfig.header.captureDevice = mSession.mDeviceHandle;
-                hidlConfig.header.captureHandle = mSession.mIoHandle;
+                hidlConfig.base.header.captureDevice = mSession.mDeviceHandle;
+                hidlConfig.base.header.captureHandle = mSession.mIoHandle;
                 mHalService.startRecognition(mHandle, hidlConfig, this, 0);
                 setState(ModelState.ACTIVE);
             }
diff --git a/services/core/java/com/android/server/stats/StatsPullAtomService.java b/services/core/java/com/android/server/stats/StatsPullAtomService.java
index e367f28..a75f1c2 100644
--- a/services/core/java/com/android/server/stats/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/StatsPullAtomService.java
@@ -16,11 +16,166 @@
 
 package com.android.server.stats;
 
-import android.content.Context;
-import android.util.Slog;
+import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+import static android.os.Process.getUidForPid;
+import static android.os.storage.VolumeInfo.TYPE_PRIVATE;
+import static android.os.storage.VolumeInfo.TYPE_PUBLIC;
 
+import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
+import static com.android.server.stats.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
+import static com.android.server.stats.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
+import static com.android.server.stats.ProcfsMemoryUtil.forEachPid;
+import static com.android.server.stats.ProcfsMemoryUtil.readCmdlineFromProcfs;
+import static com.android.server.stats.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManagerInternal;
+import android.app.AlarmManager;
+import android.app.AlarmManager.OnAlarmListener;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalOps;
+import android.app.AppOpsManager.HistoricalOpsRequest;
+import android.app.AppOpsManager.HistoricalPackageOps;
+import android.app.AppOpsManager.HistoricalUidOps;
+import android.app.INotificationManager;
+import android.app.ProcessMemoryState;
+import android.app.StatsManager;
+import android.app.StatsManager.PullAtomMetadata;
+import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.UidTraffic;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.net.ConnectivityManager;
+import android.net.INetworkStatsService;
+import android.net.Network;
+import android.net.NetworkRequest;
+import android.net.NetworkStats;
+import android.net.wifi.WifiManager;
+import android.os.BatteryStats;
+import android.os.BatteryStatsInternal;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CoolingDevice;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.IPullAtomCallback;
+import android.os.IStatsCompanionService;
+import android.os.IStatsd;
+import android.os.IStoraged;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.StatFs;
+import android.os.StatsLogEventWrapper;
+import android.os.SynchronousResultReceiver;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Temperature;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.connectivity.WifiActivityEnergyInfo;
+import android.os.storage.DiskInfo;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.provider.Settings;
+import android.stats.storage.StorageEnums;
+import android.telephony.ModemActivityInfo;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.StatsEvent;
+import android.util.StatsLog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.procstats.IProcessStats;
+import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.internal.os.BinderCallsStats.ExportedCallStat;
+import com.android.internal.os.KernelCpuSpeedReader;
+import com.android.internal.os.KernelCpuThreadReader;
+import com.android.internal.os.KernelCpuThreadReaderDiff;
+import com.android.internal.os.KernelCpuThreadReaderSettingsObserver;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
+import com.android.internal.os.KernelWakelockReader;
+import com.android.internal.os.KernelWakelockStats;
+import com.android.internal.os.LooperStats;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.os.StoragedUidIoStatsReader;
+import com.android.internal.util.DumpUtils;
+import com.android.server.BinderCallsStatsService;
+import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.SystemServiceManager;
+import com.android.server.am.MemoryStatUtil.MemoryStat;
+import com.android.server.notification.NotificationManagerService;
+import com.android.server.role.RoleManagerInternal;
+import com.android.server.stats.IonMemoryUtil.IonAllocations;
+import com.android.server.stats.ProcfsMemoryUtil.MemorySnapshot;
+import com.android.server.storage.DiskStatsFileLogger;
+import com.android.server.storage.DiskStatsLoggingService;
+
+import com.google.android.collect.Sets;
+
+import libcore.io.IoUtils;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * SystemService containing PullAtomCallbacks that are registered with statsd.
@@ -31,13 +186,24 @@
     private static final String TAG = "StatsPullAtomService";
     private static final boolean DEBUG = true;
 
+    private final Object mNetworkStatsLock = new Object();
+    @GuardedBy("mNetworkStatsLock")
+    private INetworkStatsService mNetworkStatsService;
+    private final Object mThermalLock = new Object();
+    @GuardedBy("mThermalLock")
+    private IThermalService mThermalService;
+
+    private final Context mContext;
+    private StatsManager mStatsManager;
+
     public StatsPullAtomService(Context context) {
         super(context);
+        mContext = context;
     }
 
     @Override
     public void onStart() {
-        // No op.
+        mStatsManager = (StatsManager) mContext.getSystemService(Context.STATS_MANAGER);
     }
 
     @Override
@@ -54,5 +220,621 @@
         if (DEBUG) {
             Slog.d(TAG, "Registering all pullers with statsd");
         }
+        registerWifiBytesTransfer();
+        registerWifiBytesTransferBackground();
+        registerMobileBytesTransfer();
+        registerMobileBytesTransferBackground();
+        registerBluetoothBytesTransfer();
+        registerKernelWakelock();
+        registerCpuTimePerFreq();
+        registerCpuTimePerUid();
+        registerCpuTimePerUidFreq();
+        registerCpuActiveTime();
+        registerCpuClusterTime();
+        registerWifiActivityInfo();
+        registerModemActivityInfo();
+        registerBluetoothActivityInfo();
+        registerSystemElapsedRealtime();
+        registerSystemUptime();
+        registerRemainingBatteryCapacity();
+        registerFullBatteryCapacity();
+        registerBatteryVoltage();
+        registerBatteryLevel();
+        registerBatteryCycleCount();
+        registerProcessMemoryState();
+        registerProcessMemoryHighWaterMark();
+        registerProcessMemorySnapshot();
+        registerSystemIonHeapSize();
+        registerProcessSystemIonHeapSize();
+        registerTemperature();
+        registerCoolingDevice();
+        registerBinderCalls();
+        registerBinderCallsExceptions();
+        registerLooperStats();
+        registerDiskStats();
+        registerDirectoryUsage();
+        registerAppSize();
+        registerCategorySize();
+        registerNumFingerprintsEnrolled();
+        registerNumFacesEnrolled();
+        registerProcStats();
+        registerProcStatsPkgProc();
+        registerDiskIO();
+        registerPowerProfile();
+        registerProcessCpuTime();
+        registerCpuTimePerThreadFreq();
+        registerDeviceCalculatedPowerUse();
+        registerDeviceCalculatedPowerBlameUid();
+        registerDeviceCalculatedPowerBlameOther();
+        registerDebugElapsedClock();
+        registerDebugFailingElapsedClock();
+        registerBuildInformation();
+        registerRoleHolder();
+        registerDangerousPermissionState();
+        registerTimeZoneDataInfo();
+        registerExternalStorageInfo();
+        registerAppsOnExternalStorageInfo();
+        registerFaceSettings();
+        registerAppOps();
+        registerNotificationRemoteViews();
+        registerDangerousPermissionState();
+        registerDangerousPermissionStateSampled();
+    }
+
+    private INetworkStatsService getINetworkStatsService() {
+        synchronized (mNetworkStatsLock) {
+            if (mNetworkStatsService == null) {
+                mNetworkStatsService = INetworkStatsService.Stub.asInterface(
+                        ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+                if (mNetworkStatsService != null) {
+                    try {
+                        mNetworkStatsService.asBinder().linkToDeath(() -> {
+                            synchronized (mNetworkStatsLock) {
+                                mNetworkStatsService = null;
+                            }
+                        }, /* flags */ 0);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "linkToDeath with NetworkStatsService failed", e);
+                        mNetworkStatsService = null;
+                    }
+                }
+
+            }
+            return mNetworkStatsService;
+        }
+    }
+
+    private IThermalService getIThermalService() {
+        synchronized (mThermalLock) {
+            if (mThermalService == null) {
+                mThermalService = IThermalService.Stub.asInterface(
+                        ServiceManager.getService(Context.THERMAL_SERVICE));
+                if (mThermalService != null) {
+                    try {
+                        mThermalService.asBinder().linkToDeath(() -> {
+                            synchronized (mThermalLock) {
+                                mThermalService = null;
+                            }
+                        }, /* flags */ 0);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "linkToDeath with thermalService failed", e);
+                        mThermalService = null;
+                    }
+                }
+            }
+            return mThermalService;
+        }
+    }
+    private void registerWifiBytesTransfer() {
+        int tagId = StatsLog.WIFI_BYTES_TRANSFER;
+        PullAtomMetadata metaData = PullAtomMetadata.newBuilder()
+                .setAdditiveFields(new int[] {2, 3, 4, 5}).build();
+        mStatsManager.registerPullAtomCallback(
+                tagId,
+                metaData,
+                (atomTag, data) -> pullWifiBytesTransfer(atomTag, data),
+                Executors.newSingleThreadExecutor()
+        );
+    }
+
+    private int pullWifiBytesTransfer(int atomTag, List<StatsEvent> pulledData) {
+        INetworkStatsService networkStatsService = getINetworkStatsService();
+        if (networkStatsService == null) {
+            Slog.e(TAG, "NetworkStats Service is not available!");
+            return StatsManager.PULL_SKIP;
+        }
+        long token = Binder.clearCallingIdentity();
+        try {
+            // TODO: Consider caching the following call to get BatteryStatsInternal.
+            BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+            String[] ifaces = bs.getWifiIfaces();
+            if (ifaces.length == 0) {
+                return StatsManager.PULL_SKIP;
+            }
+            // Combine all the metrics per Uid into one record.
+            NetworkStats stats = networkStatsService.getDetailedUidStats(ifaces).groupedByUid();
+            addNetworkStats(atomTag, pulledData, stats, false);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Pulling netstats for wifi bytes has error", e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private void addNetworkStats(
+            int tag, List<StatsEvent> ret, NetworkStats stats, boolean withFGBG) {
+        int size = stats.size();
+        NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
+        for (int j = 0; j < size; j++) {
+            stats.getValues(j, entry);
+            StatsEvent.Builder e = StatsEvent.newBuilder();
+            e.setAtomId(tag);
+            e.writeInt(entry.uid);
+            if (withFGBG) {
+                e.writeInt(entry.set);
+            }
+            e.writeLong(entry.rxBytes);
+            e.writeLong(entry.rxPackets);
+            e.writeLong(entry.txBytes);
+            e.writeLong(entry.txPackets);
+            ret.add(e.build());
+        }
+    }
+
+    private void registerWifiBytesTransferBackground() {
+        // No op.
+    }
+
+    private void pullWifiBytesTransferBackground() {
+        // No op.
+    }
+
+    private void registerMobileBytesTransfer() {
+        // No op.
+    }
+
+    private void pullMobileBytesTransfer() {
+        // No op.
+    }
+
+    private void registerMobileBytesTransferBackground() {
+        // No op.
+    }
+
+    private void pullMobileBytesTransferBackground() {
+        // No op.
+    }
+
+    private void registerBluetoothBytesTransfer() {
+        // No op.
+    }
+
+    private void pullBluetoothBytesTransfer() {
+        // No op.
+    }
+
+    private void registerKernelWakelock() {
+        // No op.
+    }
+
+    private void pullKernelWakelock() {
+        // No op.
+    }
+
+    private void registerCpuTimePerFreq() {
+        // No op.
+    }
+
+    private void pullCpuTimePerFreq() {
+        // No op.
+    }
+
+    private void registerCpuTimePerUid() {
+        // No op.
+    }
+
+    private void pullCpuTimePerUid() {
+        // No op.
+    }
+
+    private void registerCpuTimePerUidFreq() {
+        // No op.
+    }
+
+    private void pullCpuTimeperUidFreq() {
+        // No op.
+    }
+
+    private void registerCpuActiveTime() {
+        // No op.
+    }
+
+    private void pullCpuActiveTime() {
+        // No op.
+    }
+
+    private void registerCpuClusterTime() {
+        // No op.
+    }
+
+    private int pullCpuClusterTime() {
+        return 0;
+    }
+
+    private void registerWifiActivityInfo() {
+        // No op.
+    }
+
+    private void pullWifiActivityInfo() {
+        // No op.
+    }
+
+    private void registerModemActivityInfo() {
+        // No op.
+    }
+
+    private void pullModemActivityInfo() {
+        // No op.
+    }
+
+    private void registerBluetoothActivityInfo() {
+        // No op.
+    }
+
+    private void pullBluetoothActivityInfo() {
+        // No op.
+    }
+
+    private void registerSystemElapsedRealtime() {
+        // No op.
+    }
+
+    private void pullSystemElapsedRealtime() {
+        // No op.
+    }
+
+    private void registerSystemUptime() {
+        // No op.
+    }
+
+    private void pullSystemUptime() {
+        // No op.
+    }
+
+    private void registerRemainingBatteryCapacity() {
+        // No op.
+    }
+
+    private void pullRemainingBatteryCapacity() {
+        // No op.
+    }
+
+    private void registerFullBatteryCapacity() {
+        // No op.
+    }
+
+    private void pullFullBatteryCapacity() {
+        // No op.
+    }
+
+    private void registerBatteryVoltage() {
+        // No op.
+    }
+
+    private void pullBatteryVoltage() {
+        // No op.
+    }
+
+    private void registerBatteryLevel() {
+        // No op.
+    }
+
+    private void pullBatteryLevel() {
+        // No op.
+    }
+
+    private void registerBatteryCycleCount() {
+        // No op.
+    }
+
+    private void pullBatteryCycleCount() {
+        // No op.
+    }
+
+    private void registerProcessMemoryState() {
+        // No op.
+    }
+
+    private void pullProcessMemoryState() {
+        // No op.
+    }
+
+    private void registerProcessMemoryHighWaterMark() {
+        // No op.
+    }
+
+    private void pullProcessMemoryHighWaterMark() {
+        // No op.
+    }
+
+    private void registerProcessMemorySnapshot() {
+        // No op.
+    }
+
+    private void pullProcessMemorySnapshot() {
+        // No op.
+    }
+
+    private void registerSystemIonHeapSize() {
+        // No op.
+    }
+
+    private void pullSystemIonHeapSize() {
+        // No op.
+    }
+
+    private void registerProcessSystemIonHeapSize() {
+        // No op.
+    }
+
+    private void pullProcessSystemIonHeapSize() {
+        // No op.
+    }
+
+    private void registerTemperature() {
+        // No op.
+    }
+
+    private void pullTemperature() {
+        // No op.
+    }
+
+    private void registerCoolingDevice() {
+        // No op.
+    }
+
+    private void pullCooldownDevice() {
+        // No op.
+    }
+
+    private void registerBinderCalls() {
+        // No op.
+    }
+
+    private void pullBinderCalls() {
+        // No op.
+    }
+
+    private void registerBinderCallsExceptions() {
+        // No op.
+    }
+
+    private void pullBinderCallsExceptions() {
+        // No op.
+    }
+
+    private void registerLooperStats() {
+        // No op.
+    }
+
+    private void pullLooperStats() {
+        // No op.
+    }
+
+    private void registerDiskStats() {
+        // No op.
+    }
+
+    private void pullDiskStats() {
+        // No op.
+    }
+
+    private void registerDirectoryUsage() {
+        // No op.
+    }
+
+    private void pullDirectoryUsage() {
+        // No op.
+    }
+
+    private void registerAppSize() {
+        // No op.
+    }
+
+    private void pullAppSize() {
+        // No op.
+    }
+
+    private void registerCategorySize() {
+        // No op.
+    }
+
+    private void pullCategorySize() {
+        // No op.
+    }
+
+    private void registerNumFingerprintsEnrolled() {
+        // No op.
+    }
+
+    private void pullNumFingerprintsEnrolled() {
+        // No op.
+    }
+
+    private void registerNumFacesEnrolled() {
+        // No op.
+    }
+
+    private void pullNumFacesEnrolled() {
+        // No op.
+    }
+
+    private void registerProcStats() {
+        // No op.
+    }
+
+    private void pullProcStats() {
+        // No op.
+    }
+
+    private void registerProcStatsPkgProc() {
+        // No op.
+    }
+
+    private void pullProcStatsPkgProc() {
+        // No op.
+    }
+
+    private void registerDiskIO() {
+        // No op.
+    }
+
+    private void pullDiskIO() {
+        // No op.
+    }
+
+    private void registerPowerProfile() {
+        // No op.
+    }
+
+    private void pullPowerProfile() {
+        // No op.
+    }
+
+    private void registerProcessCpuTime() {
+        // No op.
+    }
+
+    private void pullProcessCpuTime() {
+        // No op.
+    }
+
+    private void registerCpuTimePerThreadFreq() {
+        // No op.
+    }
+
+    private void pullCpuTimePerThreadFreq() {
+        // No op.
+    }
+
+    private void registerDeviceCalculatedPowerUse() {
+        // No op.
+    }
+
+    private void pullDeviceCalculatedPowerUse() {
+        // No op.
+    }
+
+    private void registerDeviceCalculatedPowerBlameUid() {
+        // No op.
+    }
+
+    private void pullDeviceCalculatedPowerBlameUid() {
+        // No op.
+    }
+
+    private void registerDeviceCalculatedPowerBlameOther() {
+        // No op.
+    }
+
+    private void pullDeviceCalculatedPowerBlameOther() {
+        // No op.
+    }
+
+    private void registerDebugElapsedClock() {
+        // No op.
+    }
+
+    private void pullDebugElapsedClock() {
+        // No op.
+    }
+
+    private void registerDebugFailingElapsedClock() {
+        // No op.
+    }
+
+    private void pullDebugFailingElapsedClock() {
+        // No op.
+    }
+
+    private void registerBuildInformation() {
+        // No op.
+    }
+
+    private void pullBuildInformation() {
+        // No op.
+    }
+
+    private void registerRoleHolder() {
+        // No op.
+    }
+
+    private void pullRoleHolder() {
+        // No op.
+    }
+
+    private void registerDangerousPermissionState() {
+        // No op.
+    }
+
+    private void pullDangerousPermissionState() {
+        // No op.
+    }
+
+    private void registerTimeZoneDataInfo() {
+        // No op.
+    }
+
+    private void pullTimeZoneDataInfo() {
+        // No op.
+    }
+
+    private void registerExternalStorageInfo() {
+        // No op.
+    }
+
+    private void pullExternalStorageInfo() {
+        // No op.
+    }
+
+    private void registerAppsOnExternalStorageInfo() {
+        // No op.
+    }
+
+    private void pullAppsOnExternalStorageInfo() {
+        // No op.
+    }
+
+    private void registerFaceSettings() {
+        // No op.
+    }
+
+    private void pullRegisterFaceSettings() {
+        // No op.
+    }
+
+    private void registerAppOps() {
+        // No op.
+    }
+
+    private void pullAppOps() {
+        // No op.
+    }
+
+    private void registerNotificationRemoteViews() {
+        // No op.
+    }
+
+    private void pullNotificationRemoteViews() {
+        // No op.
+    }
+
+    private void registerDangerousPermissionStateSampled() {
+        // No op.
+    }
+
+    private void pullDangerousPermissionStateSampled() {
+        // No op.
     }
 }
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index 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/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index c959439..9fd3ea4 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -163,6 +163,7 @@
 import android.view.DisplayInfo;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
+import android.view.ITaskOrganizer;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -803,6 +804,16 @@
         setWindowingMode(windowingMode, false /* animate */, false /* showRecents */,
                 false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,
                 false /* creating */);
+
+        windowingMode = getWindowingMode();
+        /*
+         * Different windowing modes may be managed by different task organizers. If
+         * getTaskOrganizer returns null, we still call transferToTaskOrganizer to
+         * make sure we clear it.
+         */
+        final ITaskOrganizer org =
+            mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode);
+        transferToTaskOrganizer(org);
     }
 
     /**
@@ -1650,6 +1661,33 @@
     }
 
     /**
+     * Indicate whether the first task in this stack is controlled by a TaskOrganizer. We aren't
+     * expecting to use the TaskOrganizer in multiple task per stack scenarios so checking
+     * the first one is ok.
+     */
+    boolean isControlledByTaskOrganizer() {
+        return getChildCount() > 0 && getTopMostTask().mTaskOrganizer != null;
+    }
+
+    private static void transferSingleTaskToOrganizer(Task tr, ITaskOrganizer organizer) {
+        tr.setTaskOrganizer(organizer);
+    }
+
+    /**
+     * Transfer control of the leashes and IWindowContainers to the given ITaskOrganizer.
+     * This will (or shortly there-after) invoke the taskAppeared callbacks.
+     * If the tasks had a previous TaskOrganizer, setTaskOrganizer will take care of
+     * emitting the taskVanished callbacks.
+     */
+    void transferToTaskOrganizer(ITaskOrganizer organizer) {
+        final PooledConsumer c = PooledLambda.obtainConsumer(
+                ActivityStack::transferSingleTaskToOrganizer,
+                PooledLambda.__(Task.class), organizer);
+        forAllTasks(c);
+        c.recycle();
+    }
+
+    /**
      * Returns true if the stack should be visible.
      *
      * @param starting The currently starting activity or null if there is none.
@@ -3577,6 +3615,15 @@
     void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds, int animationDuration,
             boolean fromFullscreen) {
         if (!inPinnedWindowingMode()) return;
+
+        /**
+         * TODO(b/146594635): Remove all PIP animation code from WM once SysUI handles animation.
+         * If this PIP Task is controlled by a TaskOrganizer, the animation occurs entirely
+         * on the TaskOrganizer side, so we just hand over the leash without doing any animation.
+         * We have to be careful to not schedule the enter-pip callback as the TaskOrganizer
+         * needs to have flexibility to schedule that at an appropriate point in the animation.
+         */
+        if (isControlledByTaskOrganizer()) return;
         if (toBounds == null /* toFullscreen */) {
             final Configuration parentConfig = getParent().getConfiguration();
             final ActivityRecord top = topRunningNonOverlayTaskActivity();
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index d63165a..aa90248 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -2548,6 +2548,7 @@
         final PooledConsumer c = PooledLambda.obtainConsumer(
                 ActivityRecord::updatePictureInPictureMode,
                 PooledLambda.__(ActivityRecord.class), targetStackBounds, forceUpdate);
+        task.getStack().setBounds(targetStackBounds);
         task.forAllActivities(c);
         c.recycle();
     }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 474c5c9..ded603c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -226,6 +226,7 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.view.IRecentsAnimationRunner;
+import android.view.ITaskOrganizer;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
 import android.view.WindowContainerTransaction;
@@ -662,6 +663,12 @@
 
     private FontScaleSettingObserver mFontScaleSettingObserver;
 
+    /**
+     * Stores the registration and state of TaskOrganizers in use.
+     */
+    TaskOrganizerController mTaskOrganizerController =
+        new TaskOrganizerController(this, mGlobalLock);
+
     private int mDeviceOwnerUid = Process.INVALID_UID;
 
     private final class FontScaleSettingObserver extends ContentObserver {
@@ -1271,6 +1278,14 @@
                 .execute();
     }
 
+    @Override
+    public final void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {
+        enforceCallerIsRecentsOrHasPermission(
+                MANAGE_ACTIVITY_STACKS, "registerTaskOrganizer()");
+        synchronized (mGlobalLock) {
+            mTaskOrganizerController.registerTaskOrganizer(organizer, windowingMode);
+        }
+    }
 
     @Override
     public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) {
@@ -3319,6 +3334,18 @@
         }
     }
 
+    private void applyWindowContainerChange(ConfigurationContainer cc,
+            WindowContainerTransaction.Change c) {
+        sanitizeAndApplyConfigChange(cc, c);
+
+        Rect enterPipBounds = c.getEnterPipBounds();
+        if (enterPipBounds != null) {
+            Task tr = (Task) cc;
+            mStackSupervisor.updatePictureInPictureMode(tr,
+                    enterPipBounds, true);
+        }
+    }
+
     @Override
     public void applyContainerTransaction(WindowContainerTransaction t) {
         mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "applyContainerTransaction()");
@@ -3335,7 +3362,7 @@
                             entries.next();
                     final ConfigurationContainer cc = ConfigurationContainer.RemoteToken.fromBinder(
                             entry.getKey()).getContainer();
-                    sanitizeAndApplyConfigChange(cc, entry.getValue());
+                    applyWindowContainerChange(cc, entry.getValue());
                 }
             }
         } finally {
@@ -4057,7 +4084,11 @@
                     throw new IllegalArgumentException("Stack: " + stack
                             + " doesn't support animated resize.");
                 }
-                if (animate) {
+                /**
+                 * TODO(b/146594635): Remove all PIP animation code from WM
+                 * once SysUI handles animation. Don't even try to animate TaskOrganized tasks.
+                 */
+                if (animate && !stack.isControlledByTaskOrganizer()) {
                     stack.animateResizePinnedStack(null /* destBounds */,
                             null /* sourceHintBounds */, animationDuration,
                             false /* fromFullscreen */);
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index dd3365c..d0310f1 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -666,4 +666,8 @@
             return sb.toString();
         }
     }
+
+    RemoteToken getRemoteToken() {
+        return mRemoteToken;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index f9ad03f..9c62e99 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2061,7 +2061,8 @@
                         cf.set(displayFrames.mRestricted);
                     }
                     applyStableConstraints(sysUiFl, fl, cf, displayFrames);
-                    if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+                    if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+                            && adjust != SOFT_INPUT_ADJUST_NOTHING) {
                         vf.set(displayFrames.mCurrent);
                     } else {
                         vf.set(cf);
@@ -2138,7 +2139,8 @@
 
                 applyStableConstraints(sysUiFl, fl, cf, displayFrames);
 
-                if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+                if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+                        && adjust != SOFT_INPUT_ADJUST_NOTHING) {
                     vf.set(displayFrames.mCurrent);
                 } else {
                     vf.set(cf);
@@ -2179,7 +2181,8 @@
                         cf.set(displayFrames.mContent);
                         df.set(displayFrames.mContent);
                     }
-                    if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+                    if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+                            && adjust != SOFT_INPUT_ADJUST_NOTHING) {
                         vf.set(displayFrames.mCurrent);
                     } else {
                         vf.set(cf);
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index c4b67d7..091f66c 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -279,6 +279,19 @@
         // we avoid reintroducing this concept by just choosing one of them here.
         inputWindowHandle.surfaceInset = child.getAttrs().surfaceInsets.left;
 
+        /**
+         * If the window is in a TaskManaged by a TaskOrganizer then most cropping
+         * will be applied using the SurfaceControl hierarchy from the Organizer.
+         * This means we need to make sure that these changes in crop are reflected
+         * in the input windows, and so ensure this flag is set so that
+         * the input crop always reflects the surface hierarchy.
+         * we may have some issues with modal-windows, but I guess we can
+         * cross that bridge when we come to implementing full-screen TaskOrg
+         */
+        if (child.getTask() != null && child.getTask().isControlledByTaskOrganizer()) {
+            inputWindowHandle.replaceTouchableRegionWithCrop(null /* Use this surfaces crop */);
+        }
+
         if (child.mGlobalScale != 1) {
             // If we are scaling the window, input coordinates need
             // to be inversely scaled to map from what is on screen
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index a13383d..5a591ec 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -129,6 +129,7 @@
         if (win == null) {
             setServerVisible(false);
             mSource.setFrame(new Rect());
+            mSource.setVisibleFrame(null);
         } else if (mControllable) {
             mWin.setControllableInsetProvider(this);
             if (mControlTarget != null) {
@@ -160,6 +161,15 @@
             mTmpRect.inset(mWin.mGivenContentInsets);
         }
         mSource.setFrame(mTmpRect);
+
+        if (mWin.mGivenVisibleInsets.left != 0 || mWin.mGivenVisibleInsets.top != 0
+                || mWin.mGivenVisibleInsets.right != 0 || mWin.mGivenVisibleInsets.bottom != 0) {
+            mTmpRect.set(mWin.getFrameLw());
+            mTmpRect.inset(mWin.mGivenVisibleInsets);
+            mSource.setVisibleFrame(mTmpRect);
+        } else {
+            mSource.setVisibleFrame(null);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index a7bf660..c3e815d 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2168,12 +2168,18 @@
             mService.continueWindowLayout();
         }
 
+        // TODO(b/146594635): Remove all PIP animation code from WM once SysUI handles animation.
         // Notify the pinned stack controller to prepare the PiP animation, expect callback
-        // delivered from SystemUI to WM to start the animation.
-        final PinnedStackController pinnedStackController =
+        // delivered from SystemUI to WM to start the animation. Unless we are using
+        // the TaskOrganizer in which case the animation will be entirely handled
+        // on that side.
+        if (mService.mTaskOrganizerController.getTaskOrganizer(WINDOWING_MODE_PINNED)
+                == null) {
+            final PinnedStackController pinnedStackController =
                 display.mDisplayContent.getPinnedStackController();
-        pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio,
-                null /* stackBounds */);
+            pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio,
+                    null /* stackBounds */);
+        }
 
         // TODO: revisit the following statement after the animation is moved from WM to SysUI.
         // Update the visibility of all activities after the they have been reparented to the new
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index eaa0ea7..399c5d3 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -16,12 +16,8 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.AnimationSpecProto.ROTATE;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.RotationAnimationSpecProto.DURATION_MS;
-import static com.android.server.wm.RotationAnimationSpecProto.END_LUMA;
-import static com.android.server.wm.RotationAnimationSpecProto.START_LUMA;
 import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING;
 import static com.android.server.wm.ScreenRotationAnimationProto.STARTED;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -29,9 +25,7 @@
 import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
 import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER;
 
-import android.animation.ArgbEvaluator;
 import android.content.Context;
-import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -46,9 +40,7 @@
 import android.view.animation.AnimationUtils;
 import android.view.animation.Transformation;
 
-import com.android.internal.R;
 import com.android.server.protolog.common.ProtoLog;
-import com.android.server.wm.utils.RotationAnimationUtils;
 
 import java.io.PrintWriter;
 
@@ -68,10 +60,10 @@
  *      animation first rotate the new content into the old orientation to then be able to
  *      animate to the new orientation
  *
- * <li> The Background color frame: <p>
- *      To have the animation seem more seamless, we add a color transitioning background behind the
- *      exiting and entering layouts. We compute the brightness of the start and end
- *      layouts and transition from the two brightness values as grayscale underneath the animation
+ * <li> The exiting Blackframe: <p>
+ *     Because the change of orientation might change the width and height of the content (i.e
+ *     when rotating from portrait to landscape) we "crop" the new content using black frames
+ *     around the screenshot so the new content does not go beyond the screenshot's bounds
  *
  * <li> The entering Blackframe: <p>
  *     The enter Blackframe is similar to the exit Blackframe but is only used when a custom
@@ -89,6 +81,8 @@
      */
     private static final int SCREEN_FREEZE_LAYER_BASE = WINDOW_FREEZE_LAYER + TYPE_LAYER_MULTIPLIER;
     private static final int SCREEN_FREEZE_LAYER_ENTER = SCREEN_FREEZE_LAYER_BASE;
+    private static final int SCREEN_FREEZE_LAYER_SCREENSHOT = SCREEN_FREEZE_LAYER_BASE + 1;
+    private static final int SCREEN_FREEZE_LAYER_EXIT = SCREEN_FREEZE_LAYER_BASE + 2;
 
     private final Context mContext;
     private final DisplayContent mDisplayContent;
@@ -96,18 +90,16 @@
     private final Transformation mRotateExitTransformation = new Transformation();
     private final Transformation mRotateEnterTransformation = new Transformation();
     // Complete transformations being applied.
+    private final Transformation mExitTransformation = new Transformation();
     private final Transformation mEnterTransformation = new Transformation();
+    private final Matrix mFrameInitialMatrix = new Matrix();
     private final Matrix mSnapshotInitialMatrix = new Matrix();
+    private final Matrix mSnapshotFinalMatrix = new Matrix();
+    private final Matrix mExitFrameFinalMatrix = new Matrix();
     private final WindowManagerService mService;
-    /** Only used for custom animations and not screen rotation. */
     private SurfaceControl mEnterBlackFrameLayer;
-    /** This layer contains the actual screenshot that is to be faded out. */
-    private SurfaceControl mScreenshotLayer;
-    /**
-     * Only used for screen rotation and not custom animations. Layered behind all other layers
-     * to avoid showing any "empty" spots
-     */
-    private SurfaceControl mBackColorSurface;
+    private SurfaceControl mRotationLayer;
+    private SurfaceControl mSurfaceControl;
     private BlackFrame mEnteringBlackFrame;
     private int mWidth, mHeight;
 
@@ -128,11 +120,8 @@
     private boolean mFinishAnimReady;
     private long mFinishAnimStartTime;
     private boolean mForceDefaultOrientation;
+    private BlackFrame mExitingBlackFrame;
     private SurfaceRotationAnimationController mSurfaceRotationAnimationController;
-    /** Intensity of light/whiteness of the layout before rotation occurs. */
-    private float mStartLuma;
-    /** Intensity of light/whiteness of the layout after rotation occurs. */
-    private float mEndLuma;
 
     public ScreenRotationAnimation(Context context, DisplayContent displayContent,
             boolean fixedToUserRotation, boolean isSecure, WindowManagerService service) {
@@ -173,15 +162,9 @@
 
         final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
         try {
-            mBackColorSurface = displayContent.makeChildSurface(null)
-                    .setName("BackColorSurface")
-                    .setColorLayer()
-                    .build();
-
-            mScreenshotLayer = displayContent.makeOverlay()
+            mRotationLayer = displayContent.makeOverlay()
                     .setName("RotationLayer")
-                    .setBufferSize(mWidth, mHeight)
-                    .setSecure(isSecure)
+                    .setContainerLayer()
                     .build();
 
             mEnterBlackFrameLayer = displayContent.makeOverlay()
@@ -189,21 +172,26 @@
                     .setContainerLayer()
                     .build();
 
+            mSurfaceControl = mService.makeSurfaceBuilder(null)
+                    .setName("ScreenshotSurface")
+                    .setParent(mRotationLayer)
+                    .setBufferSize(mWidth, mHeight)
+                    .setSecure(isSecure)
+                    .build();
+
             // In case display bounds change, screenshot buffer and surface may mismatch so set a
             // scaling mode.
             SurfaceControl.Transaction t2 = mService.mTransactionFactory.get();
-            t2.setOverrideScalingMode(mScreenshotLayer, Surface.SCALING_MODE_SCALE_TO_WINDOW);
+            t2.setOverrideScalingMode(mSurfaceControl, Surface.SCALING_MODE_SCALE_TO_WINDOW);
             t2.apply(true /* sync */);
 
             // Capture a screenshot into the surface we just created.
             final int displayId = display.getDisplayId();
             final Surface surface = mService.mSurfaceFactory.get();
-            surface.copyFrom(mScreenshotLayer);
+            surface.copyFrom(mSurfaceControl);
             SurfaceControl.ScreenshotGraphicBuffer gb =
                     mService.mDisplayManagerInternal.screenshot(displayId);
             if (gb != null) {
-                mStartLuma = RotationAnimationUtils.getAvgBorderLuma(gb.getGraphicBuffer(),
-                        gb.getColorSpace());
                 try {
                     surface.attachAndQueueBufferWithColorSpace(gb.getGraphicBuffer(),
                             gb.getColorSpace());
@@ -214,15 +202,13 @@
                 // screenshot surface we display it in also has FLAG_SECURE so that
                 // the user can not screenshot secure layers via the screenshot surface.
                 if (gb.containsSecureLayers()) {
-                    t.setSecure(mScreenshotLayer, true);
+                    t.setSecure(mSurfaceControl, true);
                 }
-                t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);
-                t.reparent(mBackColorSurface, displayContent.getSurfaceControl());
-                t.setLayer(mBackColorSurface, -1);
-                t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
-                t.setAlpha(mBackColorSurface, 1);
-                t.show(mScreenshotLayer);
-                t.show(mBackColorSurface);
+                t.setLayer(mRotationLayer, SCREEN_FREEZE_LAYER_BASE);
+                t.setLayer(mSurfaceControl, SCREEN_FREEZE_LAYER_SCREENSHOT);
+                t.setAlpha(mSurfaceControl, 0);
+                t.show(mRotationLayer);
+                t.show(mSurfaceControl);
             } else {
                 Slog.w(TAG, "Unable to take screenshot of display " + displayId);
             }
@@ -232,11 +218,32 @@
         }
 
         ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
-                    "  FREEZE %s: CREATE", mScreenshotLayer);
+                    "  FREEZE %s: CREATE", mSurfaceControl);
         setRotation(t, originalRotation);
         t.apply();
     }
 
+    private static void createRotationMatrix(int rotation, int width, int height,
+            Matrix outMatrix) {
+        switch (rotation) {
+            case Surface.ROTATION_0:
+                outMatrix.reset();
+                break;
+            case Surface.ROTATION_90:
+                outMatrix.setRotate(90, 0, 0);
+                outMatrix.postTranslate(height, 0);
+                break;
+            case Surface.ROTATION_180:
+                outMatrix.setRotate(180, 0, 0);
+                outMatrix.postTranslate(width, height);
+                break;
+            case Surface.ROTATION_270:
+                outMatrix.setRotate(270, 0, 0);
+                outMatrix.postTranslate(0, width);
+                break;
+        }
+    }
+
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         proto.write(STARTED, mStarted);
@@ -245,11 +252,11 @@
     }
 
     boolean hasScreenshot() {
-        return mScreenshotLayer != null;
+        return mSurfaceControl != null;
     }
 
     private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) {
-        if (mScreenshotLayer == null) {
+        if (mRotationLayer == null) {
             return;
         }
         matrix.getValues(mTmpFloats);
@@ -260,19 +267,24 @@
             x -= mCurrentDisplayRect.left;
             y -= mCurrentDisplayRect.top;
         }
-        t.setPosition(mScreenshotLayer, x, y);
-        t.setMatrix(mScreenshotLayer,
+        t.setPosition(mRotationLayer, x, y);
+        t.setMatrix(mRotationLayer,
                 mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
                 mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
 
-        t.setAlpha(mScreenshotLayer, (float) 1.0);
-        t.show(mScreenshotLayer);
+        t.setAlpha(mSurfaceControl, (float) 1.0);
+        t.setAlpha(mRotationLayer, (float) 1.0);
+        t.show(mRotationLayer);
     }
 
     public void printTo(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.print("mSurface="); pw.print(mScreenshotLayer);
+        pw.print(prefix); pw.print("mSurface="); pw.print(mSurfaceControl);
         pw.print(" mWidth="); pw.print(mWidth);
         pw.print(" mHeight="); pw.println(mHeight);
+        pw.print(prefix); pw.print("mExitingBlackFrame="); pw.println(mExitingBlackFrame);
+        if (mExitingBlackFrame != null) {
+            mExitingBlackFrame.printTo(prefix + "  ", pw);
+        }
         pw.print(prefix);
         pw.print("mEnteringBlackFrame=");
         pw.println(mEnteringBlackFrame);
@@ -291,10 +303,20 @@
         pw.print(" "); mRotateExitTransformation.printShortString(pw); pw.println();
         pw.print(prefix); pw.print("mRotateEnterAnimation="); pw.print(mRotateEnterAnimation);
         pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mExitTransformation=");
+        mExitTransformation.printShortString(pw); pw.println();
         pw.print(prefix); pw.print("mEnterTransformation=");
         mEnterTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mFrameInitialMatrix=");
+        mFrameInitialMatrix.printShortString(pw);
+        pw.println();
         pw.print(prefix); pw.print("mSnapshotInitialMatrix=");
-        mSnapshotInitialMatrix.printShortString(pw);pw.println();
+        mSnapshotInitialMatrix.printShortString(pw);
+        pw.print(" mSnapshotFinalMatrix="); mSnapshotFinalMatrix.printShortString(pw);
+        pw.println();
+        pw.print(prefix); pw.print("mExitFrameFinalMatrix=");
+        mExitFrameFinalMatrix.printShortString(pw);
+        pw.println();
         pw.print(prefix); pw.print("mForceDefaultOrientation="); pw.print(mForceDefaultOrientation);
         if (mForceDefaultOrientation) {
             pw.print(" mOriginalDisplayRect="); pw.print(mOriginalDisplayRect.toShortString());
@@ -309,7 +331,7 @@
         // to the snapshot to make it stay in the same original position
         // with the current screen rotation.
         int delta = DisplayContent.deltaRotation(rotation, Surface.ROTATION_0);
-        RotationAnimationUtils.createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
+        createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
 
         setRotationTransform(t, mSnapshotInitialMatrix);
     }
@@ -319,7 +341,7 @@
      */
     private boolean startAnimation(SurfaceControl.Transaction t, long maxAnimationDuration,
             float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
-        if (mScreenshotLayer == null) {
+        if (mSurfaceControl == null) {
             // Can't do animation.
             return false;
         }
@@ -332,58 +354,89 @@
         // Figure out how the screen has moved from the original rotation.
         int delta = DisplayContent.deltaRotation(mCurRotation, mOriginalRotation);
 
+        mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
+                com.android.internal.R.anim.screen_rotate_alpha);
 
         final boolean customAnim;
         if (exitAnim != 0 && enterAnim != 0) {
             customAnim = true;
             mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim);
             mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim);
-            mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
-                    R.anim.screen_rotate_alpha);
         } else {
             customAnim = false;
-            switch (delta) { /* Counter-Clockwise Rotations */
+            switch (delta) {
                 case Surface.ROTATION_0:
                     mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_0_exit);
+                            com.android.internal.R.anim.screen_rotate_0_exit);
                     mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_0_enter);
+                            com.android.internal.R.anim.screen_rotate_0_enter);
                     break;
                 case Surface.ROTATION_90:
                     mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_plus_90_exit);
+                            com.android.internal.R.anim.screen_rotate_plus_90_exit);
                     mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_plus_90_enter);
+                            com.android.internal.R.anim.screen_rotate_plus_90_enter);
                     break;
                 case Surface.ROTATION_180:
                     mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_180_exit);
+                            com.android.internal.R.anim.screen_rotate_180_exit);
                     mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_180_enter);
+                            com.android.internal.R.anim.screen_rotate_180_enter);
                     break;
                 case Surface.ROTATION_270:
                     mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_minus_90_exit);
+                            com.android.internal.R.anim.screen_rotate_minus_90_exit);
                     mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                            R.anim.screen_rotate_minus_90_enter);
+                            com.android.internal.R.anim.screen_rotate_minus_90_enter);
                     break;
             }
         }
 
-        mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
-        mRotateExitAnimation.restrictDuration(maxAnimationDuration);
-        mRotateExitAnimation.scaleCurrentDuration(animationScale);
+        // Initialize the animations.  This is a hack, redefining what "parent"
+        // means to allow supplying the last and next size.  In this definition
+        // "%p" is the original (let's call it "previous") size, and "%" is the
+        // screen's current/new size.
         mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
-        mRotateEnterAnimation.restrictDuration(maxAnimationDuration);
-        mRotateEnterAnimation.scaleCurrentDuration(animationScale);
-
+        mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
         mAnimRunning = false;
         mFinishAnimReady = false;
         mFinishAnimStartTime = -1;
 
-        if (customAnim) {
-            mRotateAlphaAnimation.restrictDuration(maxAnimationDuration);
-            mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
+        mRotateExitAnimation.restrictDuration(maxAnimationDuration);
+        mRotateExitAnimation.scaleCurrentDuration(animationScale);
+        mRotateEnterAnimation.restrictDuration(maxAnimationDuration);
+        mRotateEnterAnimation.scaleCurrentDuration(animationScale);
+        mRotateAlphaAnimation.restrictDuration(maxAnimationDuration);
+        mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
+
+        if (!customAnim && mExitingBlackFrame == null) {
+            try {
+                // Compute the transformation matrix that must be applied
+                // the the black frame to make it stay in the initial position
+                // before the new screen rotation.  This is different than the
+                // snapshot transformation because the snapshot is always based
+                // of the native orientation of the screen, not the orientation
+                // we were last in.
+                createRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mFrameInitialMatrix);
+
+                final Rect outer;
+                final Rect inner;
+                if (mForceDefaultOrientation) {
+                    // Going from a smaller Display to a larger Display, add curtains to sides
+                    // or top and bottom. Going from a larger to smaller display will result in
+                    // no BlackSurfaces being constructed.
+                    outer = mCurrentDisplayRect;
+                    inner = mOriginalDisplayRect;
+                } else {
+                    outer = new Rect(-mWidth, -mHeight, mWidth * 2, mHeight * 2);
+                    inner = new Rect(0, 0, mWidth, mHeight);
+                }
+                mExitingBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner,
+                        SCREEN_FREEZE_LAYER_EXIT, mDisplayContent, mForceDefaultOrientation,
+                        mRotationLayer);
+            } catch (OutOfResourcesException e) {
+                Slog.w(TAG, "Unable to allocate black surface", e);
+            }
         }
 
         if (customAnim && mEnteringBlackFrame == null) {
@@ -398,12 +451,7 @@
             }
         }
 
-        if (customAnim) {
-            mSurfaceRotationAnimationController.startCustomAnimation();
-        } else {
-            mSurfaceRotationAnimationController.startScreenRotationAnimation();
-        }
-
+        mSurfaceRotationAnimationController.startAnimation();
         return true;
     }
 
@@ -412,13 +460,11 @@
      */
     public boolean dismiss(SurfaceControl.Transaction t, long maxAnimationDuration,
             float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
-        if (mScreenshotLayer == null) {
+        if (mSurfaceControl == null) {
             // Can't do animation.
             return false;
         }
         if (!mStarted) {
-            mEndLuma = RotationAnimationUtils.getLumaOfSurfaceControl(mDisplayContent.getDisplay(),
-                    mDisplayContent.getWindowingLayer());
             startAnimation(t, maxAnimationDuration, animationScale, finalWidth, finalHeight,
                     exitAnim, enterAnim);
         }
@@ -434,28 +480,28 @@
             mSurfaceRotationAnimationController.cancel();
             mSurfaceRotationAnimationController = null;
         }
-
-        if (mScreenshotLayer != null) {
-            ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "  FREEZE %s: DESTROY", mScreenshotLayer);
+        if (mSurfaceControl != null) {
+            ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "  FREEZE %s: DESTROY", mSurfaceControl);
+            mSurfaceControl = null;
             SurfaceControl.Transaction t = mService.mTransactionFactory.get();
-            if (mScreenshotLayer.isValid()) {
-                t.remove(mScreenshotLayer);
+            if (mRotationLayer != null) {
+                if (mRotationLayer.isValid()) {
+                    t.remove(mRotationLayer);
+                }
+                mRotationLayer = null;
             }
-            mScreenshotLayer = null;
-
             if (mEnterBlackFrameLayer != null) {
                 if (mEnterBlackFrameLayer.isValid()) {
                     t.remove(mEnterBlackFrameLayer);
                 }
                 mEnterBlackFrameLayer = null;
             }
-            if (mBackColorSurface != null) {
-                t.remove(mBackColorSurface);
-                mBackColorSurface = null;
-            }
             t.apply();
         }
-
+        if (mExitingBlackFrame != null) {
+            mExitingBlackFrame.kill();
+            mExitingBlackFrame = null;
+        }
         if (mEnteringBlackFrame != null) {
             mEnteringBlackFrame.kill();
             mEnteringBlackFrame = null;
@@ -491,28 +537,18 @@
      * Utility class that runs a {@link ScreenRotationAnimation} on the {@link
      * SurfaceAnimationRunner}.
      * <p>
-     * The rotation animation supports both screen rotation and custom animations
-     *
-     * For custom animations:
+     * The rotation animation is divided into the following hierarchy:
      * <ul>
-     *   <li>
-     *     The screenshot layer which has an added animation of it's alpha channel
-     *     ("screen_rotate_alpha") and that will be applied along with the custom animation.
-     *   </li>
-     *   <li> A device layer that is animated with the provided custom animation </li>
-     * </ul>
-     *
-     * For screen rotation:
-     * <ul>
-     *   <li> A rotation layer that is both rotated and faded out during a single animation </li>
-     *   <li> A device layer that is both rotated and faded in during a single animation </li>
-     *   <li> A background color layer that transitions colors behind the first two layers </li>
-     * </ul>
-     *
+     * <li> A first rotation layer, containing the blackframes. This layer is animated by the
+     * "screen_rotate_X_exit" that applies a scale and rotate and where X is value of the rotation.
+     *     <ul>
+     *         <li> A child layer containing the screenshot on which is added an animation of it's
+     *     alpha channel ("screen_rotate_alpha") and that will rotate with his parent layer.</li>
+     *     </ul>
+     * <li> A second rotation layer used when custom animations are passed in
      * {@link ScreenRotationAnimation#startAnimation(
      *     SurfaceControl.Transaction, long, float, int, int, int, int)}.
      * </ul>
-     *
      * <p>
      * Thus an {@link LocalAnimationAdapter.AnimationSpec} is created for each of
      * this three {@link SurfaceControl}s which then delegates the animation to the
@@ -520,35 +556,22 @@
      */
     class SurfaceRotationAnimationController {
         private SurfaceAnimator mDisplayAnimator;
+        private SurfaceAnimator mEnterBlackFrameAnimator;
         private SurfaceAnimator mScreenshotRotationAnimator;
         private SurfaceAnimator mRotateScreenAnimator;
-        private SurfaceAnimator mEnterBlackFrameAnimator;
-
-        void startCustomAnimation() {
-            try {
-                mService.mSurfaceAnimationRunner.deferStartingAnimations();
-                mRotateScreenAnimator = startScreenshotAlphaAnimation();
-                mDisplayAnimator = startDisplayRotation();
-                if (mEnteringBlackFrame != null) {
-                    mEnterBlackFrameAnimator = startEnterBlackFrameAnimation();
-                }
-            } finally {
-                mService.mSurfaceAnimationRunner.continueStartingAnimations();
-            }
-        }
 
         /**
          * Start the rotation animation of the display and the screenshot on the
          * {@link SurfaceAnimationRunner}.
          */
-        void startScreenRotationAnimation() {
-            try {
-                mService.mSurfaceAnimationRunner.deferStartingAnimations();
-                mDisplayAnimator = startDisplayRotation();
+        void startAnimation() {
+            mRotateScreenAnimator = startScreenshotAlphaAnimation();
+            mDisplayAnimator = startDisplayRotation();
+            if (mExitingBlackFrame != null) {
                 mScreenshotRotationAnimator = startScreenshotRotationAnimation();
-                startColorAnimation();
-            } finally {
-                mService.mSurfaceAnimationRunner.continueStartingAnimations();
+            }
+            if (mEnteringBlackFrame != null) {
+                mEnterBlackFrameAnimator = startEnterBlackFrameAnimation();
             }
         }
 
@@ -573,8 +596,8 @@
 
         private SurfaceAnimator startScreenshotAlphaAnimation() {
             return startAnimation(initializeBuilder()
-                            .setSurfaceControl(mScreenshotLayer)
-                            .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
+                            .setSurfaceControl(mSurfaceControl)
+                            .setAnimationLeashParent(mRotationLayer)
                             .setWidth(mWidth)
                             .setHeight(mHeight)
                             .build(),
@@ -593,67 +616,13 @@
 
         private SurfaceAnimator startScreenshotRotationAnimation() {
             return startAnimation(initializeBuilder()
-                            .setSurfaceControl(mScreenshotLayer)
+                            .setSurfaceControl(mRotationLayer)
                             .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
                             .build(),
                     createWindowAnimationSpec(mRotateExitAnimation),
                     this::onAnimationEnd);
         }
 
-
-        /**
-         * Applies the color change from {@link #mStartLuma} to {@link #mEndLuma} as a
-         * grayscale color
-         */
-        private void startColorAnimation() {
-            int colorTransitionMs = mContext.getResources().getInteger(
-                    R.integer.config_screen_rotation_color_transition);
-            final SurfaceAnimationRunner runner = mService.mSurfaceAnimationRunner;
-            final float[] rgbTmpFloat = new float[3];
-            final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
-            final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
-            final long duration = colorTransitionMs * (long) mService.getCurrentAnimatorScale();
-            final ArgbEvaluator va = ArgbEvaluator.getInstance();
-            runner.startAnimation(
-                new LocalAnimationAdapter.AnimationSpec() {
-                    @Override
-                    public long getDuration() {
-                        return duration;
-                    }
-
-                    @Override
-                    public void apply(SurfaceControl.Transaction t, SurfaceControl leash,
-                        long currentPlayTime) {
-                        float fraction = (float)currentPlayTime / (float)getDuration();
-                        int color = (Integer) va.evaluate(fraction, startColor, endColor);
-                        Color middleColor = Color.valueOf(color);
-                        rgbTmpFloat[0] = middleColor.red();
-                        rgbTmpFloat[1] = middleColor.green();
-                        rgbTmpFloat[2] = middleColor.blue();
-                        if (leash.isValid()) {
-                            t.setColor(leash, rgbTmpFloat);
-                        }
-                    }
-
-                    @Override
-                    public void dump(PrintWriter pw, String prefix) {
-                        pw.println(prefix + "startLuma=" + mStartLuma
-                                + " endLuma=" + mEndLuma
-                                + " durationMs=" + colorTransitionMs);
-                    }
-
-                    @Override
-                    public void dumpDebugInner(ProtoOutputStream proto) {
-                        final long token = proto.start(ROTATE);
-                        proto.write(START_LUMA, mStartLuma);
-                        proto.write(END_LUMA, mEndLuma);
-                        proto.write(DURATION_MS, colorTransitionMs);
-                        proto.end(token);
-                    }
-                },
-                mBackColorSurface, mDisplayContent.getPendingTransaction(), null);
-        }
-
         private WindowAnimationSpec createWindowAnimationSpec(Animation mAnimation) {
             return new WindowAnimationSpec(mAnimation, new Point(0, 0) /* position */,
                     false /* canSkipFirstFrame */, 0 /* WindowCornerRadius */);
@@ -677,6 +646,7 @@
 
             LocalAnimationAdapter localAnimationAdapter = new LocalAnimationAdapter(
                     animationSpec, mService.mSurfaceAnimationRunner);
+
             animator.startAnimation(mDisplayContent.getPendingTransaction(),
                     localAnimationAdapter, false);
             return animator;
@@ -722,6 +692,7 @@
             if (mEnterBlackFrameAnimator != null) {
                 mEnterBlackFrameAnimator.cancelAnimation();
             }
+
             if (mScreenshotRotationAnimator != null) {
                 mScreenshotRotationAnimator.cancelAnimation();
             }
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 5633b6b..50cea2e 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -78,10 +78,6 @@
     @GuardedBy("mLock")
     private boolean mAnimationStartDeferred;
 
-    /**
-     * There should only ever be one instance of this class. Usual spot for it is with
-     * {@link WindowManagerService}
-     */
     SurfaceAnimationRunner(Supplier<Transaction> transactionFactory,
             PowerManagerInternal powerManagerInternal) {
         this(null /* callbackProvider */, null /* animatorFactory */,
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9a140da..5cb7091 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -117,9 +117,11 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -130,6 +132,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
+import android.view.ITaskOrganizer;
 import android.view.RemoteAnimationTarget;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -425,6 +428,14 @@
     }
 
     /**
+     * The TaskOrganizer which is delegated presentation of this task. If set the Task will
+     * emit an IWindowContainer (allowing access to it's SurfaceControl leash) to the organizers
+     * taskAppeared callback, and emit a taskRemoved callback when the Task is vanished.
+     */
+    ITaskOrganizer mTaskOrganizer;
+
+
+    /**
      * Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int,
      * ActivityInfo, Intent, TaskDescription)} instead.
      */
@@ -445,6 +456,22 @@
                 _voiceSession, _voiceInteractor, stack);
     }
 
+    class TaskToken extends RemoteToken {
+        TaskToken(ConfigurationContainer container) {
+            super(container);
+        }
+
+        @Override
+        public SurfaceControl getLeash() {
+            // We need to copy the SurfaceControl instead of returning the original
+            // because the Parcel FLAGS PARCELABLE_WRITE_RETURN_VALUE cause SurfaceControls
+            // to release themselves.
+            SurfaceControl sc = new SurfaceControl();
+            sc.copyFrom(getSurfaceControl());
+            return sc;
+        }
+    }
+
     /** Don't use constructor directly. This is only used by XML parser. */
     Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
             Intent _affinityIntent, String _affinity, String _rootAffinity,
@@ -469,7 +496,7 @@
         mTaskDescription = _lastTaskDescription;
         // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
         setOrientation(SCREEN_ORIENTATION_UNSET);
-        mRemoteToken = new RemoteToken(this);
+        mRemoteToken = new TaskToken(this);
         affinityIntent = _affinityIntent;
         affinity = _affinity;
         rootAffinity = _rootAffinity;
@@ -2179,6 +2206,10 @@
     void removeImmediately() {
         if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId);
         EventLogTags.writeWmTaskRemoved(mTaskId, "removeTask");
+
+        // If applicable let the TaskOrganizer know the Task is vanishing.
+        setTaskOrganizer(null);
+
         super.removeImmediately();
     }
 
@@ -2567,6 +2598,12 @@
     }
 
     boolean shouldAnimate() {
+        /**
+         * Animations are handled by the TaskOrganizer implementation.
+         */
+        if (isControlledByTaskOrganizer()) {
+            return false;
+        }
         // Don't animate while the task runs recents animation but only if we are in the mode
         // where we cancel with deferred screenshot, which means that the controller has
         // transformed the task.
@@ -3444,4 +3481,91 @@
             XmlUtils.skipCurrentTag(in);
         }
     }
+
+    boolean isControlledByTaskOrganizer() {
+        return mTaskOrganizer != null;
+    }
+
+    @Override
+    protected void reparentSurfaceControl(SurfaceControl.Transaction t, SurfaceControl newParent) {
+        /**
+         * Avoid yanking back control from the TaskOrganizer, which has presumably reparented the
+         * Surface in to its own hierarchy.
+         */
+        if (isControlledByTaskOrganizer()) {
+            return;
+        }
+        super.reparentSurfaceControl(t, newParent);
+    }
+
+    private void sendTaskAppeared() {
+        if (mSurfaceControl != null && mTaskOrganizer != null) {
+            mAtmService.mTaskOrganizerController.onTaskAppeared(mTaskOrganizer, this);
+        }
+    }
+
+    private void sendTaskVanished() {
+        if (mTaskOrganizer != null) {
+            mAtmService.mTaskOrganizerController.onTaskVanished(mTaskOrganizer, this);
+        }
+   }
+
+    void setTaskOrganizer(ITaskOrganizer organizer) {
+        // Let the old organizer know it has lost control.
+        if (mTaskOrganizer != null) {
+            sendTaskVanished();
+        }
+        mTaskOrganizer = organizer;
+        sendTaskAppeared();
+    }
+
+    // Called on Binder death.
+    void taskOrganizerDied() {
+        mTaskOrganizer = null;
+    }
+
+    @Override
+    void setSurfaceControl(SurfaceControl sc) {
+        super.setSurfaceControl(sc);
+        // If the TaskOrganizer was set before we created the SurfaceControl, we need to
+        // emit the callbacks now.
+        sendTaskAppeared();
+    }
+
+    @Override
+    public void updateSurfacePosition() {
+        // Avoid fighting with the TaskOrganizer over Surface position.
+        if (isControlledByTaskOrganizer()) {
+            getPendingTransaction().setPosition(mSurfaceControl, 0, 0);
+            scheduleAnimation();
+            return;
+        } else {
+            super.updateSurfacePosition();
+        }
+    }
+
+    @Override
+    void getRelativeDisplayedPosition(Point outPos) {
+        // In addition to updateSurfacePosition, we keep other code that sets
+        // position from fighting with the TaskOrganizer
+        if (isControlledByTaskOrganizer()) {
+            outPos.set(0, 0);
+            return;
+        }
+        super.getRelativeDisplayedPosition(outPos);
+    }
+
+    @Override
+    public void setWindowingMode(int windowingMode) {
+        super.setWindowingMode(windowingMode);
+        windowingMode = getWindowingMode();
+        /*
+         * Different windowing modes may be managed by different task organizers. If
+         * getTaskOrganizer returns null, we still call transferToTaskOrganizer to
+         * make sure we clear it.
+         */
+        final ITaskOrganizer org =
+            mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode);
+        setTaskOrganizer(org);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
new file mode 100644
index 0000000..283be40
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.ITaskOrganizer;
+import android.view.SurfaceControl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Stores the TaskOrganizers associated with a given windowing mode and
+ * their associated state.
+ */
+class TaskOrganizerController {
+    private static final String TAG = "TaskOrganizerController";
+
+    private WindowManagerGlobalLock mGlobalLock;
+
+    private class DeathRecipient implements IBinder.DeathRecipient {
+        int mWindowingMode;
+        ITaskOrganizer mTaskOrganizer;
+
+        DeathRecipient(ITaskOrganizer organizer, int windowingMode) {
+            mTaskOrganizer = organizer;
+            mWindowingMode = windowingMode;
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (mGlobalLock) {
+                final TaskOrganizerState state = mTaskOrganizerStates.get(mTaskOrganizer);
+                for (int i = 0; i < state.mOrganizedTasks.size(); i++) {
+                    state.mOrganizedTasks.get(i).taskOrganizerDied();
+                }
+                mTaskOrganizerStates.remove(mTaskOrganizer);
+                if (mTaskOrganizersForWindowingMode.get(mWindowingMode) == mTaskOrganizer) {
+                    mTaskOrganizersForWindowingMode.remove(mWindowingMode);
+                }
+            }
+        }
+    };
+
+    class TaskOrganizerState {
+        ITaskOrganizer mOrganizer;
+        DeathRecipient mDeathRecipient;
+
+        ArrayList<Task> mOrganizedTasks = new ArrayList<>();
+
+        void addTask(Task t) {
+            mOrganizedTasks.add(t);
+        }
+
+        void removeTask(Task t) {
+            mOrganizedTasks.remove(t);
+        }
+
+        TaskOrganizerState(ITaskOrganizer organizer, DeathRecipient deathRecipient) {
+            mOrganizer = organizer;
+            mDeathRecipient = deathRecipient;
+        }
+    };
+
+
+    final HashMap<Integer, TaskOrganizerState> mTaskOrganizersForWindowingMode = new HashMap();
+    final HashMap<ITaskOrganizer, TaskOrganizerState> mTaskOrganizerStates = new HashMap();
+
+    final HashMap<Integer, ITaskOrganizer> mTaskOrganizersByPendingSyncId = new HashMap();
+
+    final ActivityTaskManagerService mService;
+
+    TaskOrganizerController(ActivityTaskManagerService atm, WindowManagerGlobalLock lock) {
+        mService = atm;
+        mGlobalLock = lock;
+    }
+
+    private void clearIfNeeded(int windowingMode) {
+        final TaskOrganizerState oldState = mTaskOrganizersForWindowingMode.get(windowingMode);
+        if (oldState != null) {
+            oldState.mOrganizer.asBinder().unlinkToDeath(oldState.mDeathRecipient, 0);
+        }
+    }
+
+    /**
+     * Register a TaskOrganizer to manage tasks as they enter the given windowing mode.
+     * If there was already a TaskOrganizer for this windowing mode it will be evicted
+     * and receive taskVanished callbacks in the process.
+     */
+    void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {
+        if (windowingMode != WINDOWING_MODE_PINNED) {
+            throw new UnsupportedOperationException(
+                    "As of now only Pinned windowing mode is supported for registerTaskOrganizer");
+
+        }
+        clearIfNeeded(windowingMode);
+        DeathRecipient dr = new DeathRecipient(organizer, windowingMode);
+        try {
+            organizer.asBinder().linkToDeath(dr, 0);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "TaskOrganizer failed to register death recipient");
+        }
+
+        final TaskOrganizerState state = new TaskOrganizerState(organizer, dr);
+        mTaskOrganizersForWindowingMode.put(windowingMode, state);
+
+        mTaskOrganizerStates.put(organizer, state);
+    }
+
+    ITaskOrganizer getTaskOrganizer(int windowingMode) {
+        final TaskOrganizerState state = mTaskOrganizersForWindowingMode.get(windowingMode);
+        if (state == null) {
+            return null;
+        }
+        return state.mOrganizer;
+    }
+
+    private void sendTaskAppeared(ITaskOrganizer organizer, Task task) {
+        try {
+            organizer.taskAppeared(task.getRemoteToken(), task.getTaskInfo());
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception sending taskAppeared callback" + e);
+        }
+    }
+
+    private void sendTaskVanished(ITaskOrganizer organizer, Task task) {
+        try {
+            organizer.taskVanished(task.getRemoteToken());
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception sending taskVanished callback" + e);
+        }
+    }
+
+    void onTaskAppeared(ITaskOrganizer organizer, Task task) {
+        TaskOrganizerState state = mTaskOrganizerStates.get(organizer);
+
+        state.addTask(task);
+        sendTaskAppeared(organizer, task);
+    }
+
+    void onTaskVanished(ITaskOrganizer organizer, Task task) {
+        final TaskOrganizerState state = mTaskOrganizerStates.get(organizer);
+        sendTaskVanished(organizer, task);
+
+        // This could trigger TaskAppeared for other tasks in the same stack so make sure
+        // we do this AFTER sending taskVanished.
+        state.removeTask(task);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 8ac212f..f3880fa 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -342,7 +342,7 @@
         if (mSurfaceControl == null) {
             // If we don't yet have a surface, but we now have a parent, we should
             // build a surface.
-            mSurfaceControl = makeSurface().build();
+            setSurfaceControl(makeSurface().build());
             getPendingTransaction().show(mSurfaceControl);
             updateSurfacePosition();
         } else {
@@ -496,7 +496,7 @@
                 mParent.getPendingTransaction().merge(getPendingTransaction());
             }
 
-            mSurfaceControl = null;
+            setSurfaceControl(null);
             mLastSurfacePosition.set(0, 0);
             scheduleAnimation();
         }
@@ -2209,4 +2209,8 @@
         }
         return mParent.getDimmer();
     }
+
+    void setSurfaceControl(SurfaceControl sc) {
+        mSurfaceControl = sc;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java b/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java
deleted file mode 100644
index 94f6676..0000000
--- a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.utils;
-
-import android.graphics.Bitmap;
-import android.graphics.ColorSpace;
-import android.graphics.GraphicBuffer;
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.view.Display;
-import android.view.Surface;
-import android.view.SurfaceControl;
-
-
-/** Helper functions for the {@link com.android.server.wm.ScreenRotationAnimation} class*/
-public class RotationAnimationUtils {
-
-    /**
-     * Converts the provided {@link GraphicBuffer} and converts it to a bitmap to then sample the
-     * luminance at the borders of the bitmap
-     * @return the average luminance of all the pixels at the borders of the bitmap
-     */
-    public static float getAvgBorderLuma(GraphicBuffer graphicBuffer, ColorSpace colorSpace) {
-        Bitmap hwBitmap = Bitmap.wrapHardwareBuffer(graphicBuffer, colorSpace);
-        if (hwBitmap == null) {
-            return 0;
-        }
-
-        Bitmap swaBitmap = hwBitmap.copy(Bitmap.Config.ARGB_8888, false);
-        float totalLuma = 0;
-        int height = swaBitmap.getHeight();
-        int width = swaBitmap.getWidth();
-        int i;
-        for (i = 0; i < width; i++) {
-            totalLuma += swaBitmap.getColor(i, 0).luminance();
-            totalLuma += swaBitmap.getColor(i, height - 1).luminance();
-        }
-        for (i = 0; i < height; i++) {
-            totalLuma += swaBitmap.getColor(0, i).luminance();
-            totalLuma += swaBitmap.getColor(width - 1, i).luminance();
-        }
-        return totalLuma / (2 * width + 2 * height);
-    }
-
-    /**
-     * Gets the average border luma by taking a screenshot of the {@param surfaceControl}.
-     * @see #getAvgBorderLuma(GraphicBuffer, ColorSpace)
-     */
-    public static float getLumaOfSurfaceControl(Display display, SurfaceControl surfaceControl) {
-        if (surfaceControl ==  null) {
-            return 0;
-        }
-
-        Point size = new Point();
-        display.getSize(size);
-        Rect crop = new Rect(0, 0, size.x, size.y);
-        SurfaceControl.ScreenshotGraphicBuffer buffer =
-                SurfaceControl.captureLayers(surfaceControl, crop, 1);
-        return RotationAnimationUtils.getAvgBorderLuma(buffer.getGraphicBuffer(),
-                buffer.getColorSpace());
-    }
-
-    public static void createRotationMatrix(int rotation, int width, int height, Matrix outMatrix) {
-        switch (rotation) {
-            case Surface.ROTATION_0:
-                outMatrix.reset();
-                break;
-            case Surface.ROTATION_90:
-                outMatrix.setRotate(90, 0, 0);
-                outMatrix.postTranslate(height, 0);
-                break;
-            case Surface.ROTATION_180:
-                outMatrix.setRotate(180, 0, 0);
-                outMatrix.postTranslate(width, height);
-                break;
-            case Surface.ROTATION_270:
-                outMatrix.setRotate(270, 0, 0);
-                outMatrix.postTranslate(0, width);
-                break;
-        }
-    }
-}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 1ad6e86..03969b0 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -13,6 +13,7 @@
     ],
 
     srcs: [
+        ":graphicsstats_proto",
         "BroadcastRadio/JavaRef.cpp",
         "BroadcastRadio/NativeCallbackThread.cpp",
         "BroadcastRadio/BroadcastRadioService.cpp",
@@ -103,6 +104,11 @@
         "libinputflinger",
         "libinputflinger_base",
         "libinputservice",
+        "libprotobuf-cpp-lite",
+        "libprotoutil",
+        "libstatspull",
+        "libstatssocket",
+        "libstatslog",
         "libschedulerservicehidl",
         "libsensorservice",
         "libsensorservicehidl",
diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp
index d1d253b..9353fbd 100644
--- a/services/core/jni/com_android_server_GraphicsStatsService.cpp
+++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp
@@ -23,6 +23,16 @@
 #include <nativehelper/ScopedUtfChars.h>
 #include <JankTracker.h>
 #include <service/GraphicsStatsService.h>
+#include <stats_pull_atom_callback.h>
+#include <stats_event.h>
+#include <statslog.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <android/util/ProtoOutputStream.h>
+#include "android/graphics/Utils.h"
+#include "core_jni_helpers.h"
+#include "protos/graphicsstats.pb.h"
+#include <cstring>
+#include <memory>
 
 namespace android {
 
@@ -77,6 +87,20 @@
     GraphicsStatsService::finishDump(dump);
 }
 
+static jlong finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr) {
+    GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
+    std::vector<uint8_t>* result = new std::vector<uint8_t>();
+    GraphicsStatsService::finishDumpInMemory(dump,
+        [](void* buffer, int bufferOffset, int bufferSize, int totalSize, void* param1, void* param2) {
+            std::vector<uint8_t>* outBuffer = reinterpret_cast<std::vector<uint8_t>*>(param2);
+            if (outBuffer->size() < totalSize) {
+                outBuffer->resize(totalSize);
+            }
+            std::memcpy(outBuffer->data() + bufferOffset, buffer, bufferSize);
+        }, env, result);
+    return reinterpret_cast<jlong>(result);
+}
+
 static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage,
         jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
     ScopedByteArrayRO buffer(env, jdata);
@@ -93,19 +117,173 @@
     GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data);
 }
 
+static jobject gGraphicsStatsServiceObject = nullptr;
+static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID;
+
+static JNIEnv* getJNIEnv() {
+    JavaVM* vm = AndroidRuntime::getJavaVM();
+    JNIEnv* env = nullptr;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) {
+            LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!");
+        }
+    }
+    return env;
+}
+
+using namespace google::protobuf;
+
+// Field ids taken from FrameTimingHistogram message in atoms.proto
+#define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1
+#define FRAME_COUNTS_FIELD_NUMBER 2
+
+static void writeCpuHistogram(stats_event* event,
+                              const uirenderer::protos::GraphicsStatsProto& stat) {
+    util::ProtoOutputStream proto;
+    for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
+        auto& bucket = stat.histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
+                            TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
+                    (int)bucket.render_millis());
+    }
+    for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
+        auto& bucket = stat.histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
+                            FRAME_COUNTS_FIELD_NUMBER /* field id */,
+                    (long long)bucket.frame_count());
+    }
+    std::vector<uint8_t> outVector;
+    proto.serializeToVector(&outVector);
+    stats_event_write_byte_array(event, outVector.data(), outVector.size());
+}
+
+static void writeGpuHistogram(stats_event* event,
+                              const uirenderer::protos::GraphicsStatsProto& stat) {
+    util::ProtoOutputStream proto;
+    for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
+        auto& bucket = stat.gpu_histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
+                            TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
+                    (int)bucket.render_millis());
+    }
+    for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
+        auto& bucket = stat.gpu_histogram(bucketIndex);
+        proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
+                            FRAME_COUNTS_FIELD_NUMBER /* field id */,
+                    (long long)bucket.frame_count());
+    }
+    std::vector<uint8_t> outVector;
+    proto.serializeToVector(&outVector);
+    stats_event_write_byte_array(event, outVector.data(), outVector.size());
+}
+
+// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom.
+static bool graphicsStatsPullCallback(int32_t atom_tag, pulled_stats_event_list* data,
+                                      const void* cookie) {
+    JNIEnv* env = getJNIEnv();
+    if (!env) {
+        return false;
+    }
+    if (gGraphicsStatsServiceObject == nullptr) {
+        ALOGE("Failed to get graphicsstats service");
+        return false;
+    }
+
+    for (bool lastFullDay : {true, false}) {
+        jlong jdata = (jlong) env->CallLongMethod(
+                    gGraphicsStatsServiceObject,
+                    gGraphicsStatsService_pullGraphicsStatsMethodID,
+                    (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE));
+        if (env->ExceptionCheck()) {
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+            ALOGE("Failed to invoke graphicsstats service");
+            return false;
+        }
+        if (!jdata) {
+            // null means data is not available for that day.
+            continue;
+        }
+        android::uirenderer::protos::GraphicsStatsServiceDumpProto serviceDump;
+        std::vector<uint8_t>* buffer = reinterpret_cast<std::vector<uint8_t>*>(jdata);
+        std::unique_ptr<std::vector<uint8_t>> bufferRelease(buffer);
+        int dataSize = buffer->size();
+        if (!dataSize) {
+            // Data is not available for that day.
+            continue;
+        }
+        io::ArrayInputStream input{buffer->data(), dataSize};
+        bool success = serviceDump.ParseFromZeroCopyStream(&input);
+        if (!success) {
+            ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'",
+                  serviceDump.InitializationErrorString().c_str(), dataSize);
+            return false;
+        }
+
+        for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) {
+            auto& stat = serviceDump.stats(stat_index);
+            stats_event* event = add_stats_event_to_pull_data(data);
+            stats_event_set_atom_id(event, android::util::GRAPHICS_STATS);
+            stats_event_write_string8(event, stat.package_name().c_str());
+            stats_event_write_int64(event, (int64_t)stat.version_code());
+            stats_event_write_int64(event, (int64_t)stat.stats_start());
+            stats_event_write_int64(event, (int64_t)stat.stats_end());
+            stats_event_write_int32(event, (int32_t)stat.pipeline());
+            stats_event_write_int32(event, (int32_t)stat.summary().total_frames());
+            stats_event_write_int32(event, (int32_t)stat.summary().missed_vsync_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().high_input_latency_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().slow_ui_thread_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().slow_bitmap_upload_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().slow_draw_count());
+            stats_event_write_int32(event, (int32_t)stat.summary().missed_deadline_count());
+            writeCpuHistogram(event, stat);
+            writeGpuHistogram(event, stat);
+            // TODO: fill in UI mainline module version, when the feature is available.
+            stats_event_write_int64(event, (int64_t)0);
+            stats_event_write_bool(event, !lastFullDay);
+            stats_event_build(event);
+        }
+    }
+    return true;
+}
+
+// Register a puller for GRAPHICS_STATS atom with the statsd service.
+static void nativeInit(JNIEnv* env, jobject javaObject) {
+    gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject);
+    pull_atom_metadata metadata = {.cool_down_ns = 10 * 1000000, // 10 milliseconds
+                                   .timeout_ns = 2 * NS_PER_SEC, // 2 seconds
+                                   .additive_fields = nullptr,
+                                   .additive_fields_size = 0};
+    register_stats_pull_atom_callback(android::util::GRAPHICS_STATS, &graphicsStatsPullCallback,
+            &metadata, nullptr);
+}
+
+static void nativeDestructor(JNIEnv* env, jobject javaObject) {
+    //TODO: Unregister the puller callback when a new API is available.
+    env->DeleteGlobalRef(gGraphicsStatsServiceObject);
+    gGraphicsStatsServiceObject = nullptr;
+}
+
 static const JNINativeMethod sMethods[] = {
     { "nGetAshmemSize", "()I", (void*) getAshmemSize },
     { "nCreateDump", "(IZ)J", (void*) createDump },
     { "nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) addToDump },
     { "nAddToDump", "(JLjava/lang/String;)V", (void*) addFileToDump },
     { "nFinishDump", "(J)V", (void*) finishDump },
+    { "nFinishDumpInMemory", "(J)J", (void*) finishDumpInMemory },
     { "nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) saveBuffer },
+    { "nativeInit", "()V", (void*) nativeInit },
+    { "nativeDestructor",   "()V",     (void*)nativeDestructor }
 };
 
 int register_android_server_GraphicsStatsService(JNIEnv* env)
 {
+    jclass graphicsStatsService_class = FindClassOrDie(env,
+            "com/android/server/GraphicsStatsService");
+    gGraphicsStatsService_pullGraphicsStatsMethodID = GetMethodIDOrDie(env,
+            graphicsStatsService_class, "pullGraphicsStats", "(Z)J");
     return jniRegisterNativeMethods(env, "com/android/server/GraphicsStatsService",
                                     sMethods, NELEM(sMethods));
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 6504e31..acb6bea 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -129,7 +129,6 @@
 using android::hardware::hidl_string;
 using android::hardware::hidl_death_recipient;
 
-using android::hardware::gnss::V1_0::GnssConstellationType;
 using android::hardware::gnss::V1_0::GnssLocationFlags;
 using android::hardware::gnss::V1_0::IAGnssRilCallback;
 using android::hardware::gnss::V1_0::IGnssGeofenceCallback;
@@ -149,6 +148,8 @@
 
 using android::hidl::base::V1_0::IBase;
 
+using GnssConstellationType_V1_0 = android::hardware::gnss::V1_0::GnssConstellationType;
+using GnssConstellationType_V2_0 = android::hardware::gnss::V2_0::GnssConstellationType;
 using GnssLocation_V1_0 = android::hardware::gnss::V1_0::GnssLocation;
 using GnssLocation_V2_0 = android::hardware::gnss::V2_0::GnssLocation;
 using IGnss_V1_0 = android::hardware::gnss::V1_0::IGnss;
@@ -161,6 +162,7 @@
 using IGnssConfiguration_V1_0 = android::hardware::gnss::V1_0::IGnssConfiguration;
 using IGnssConfiguration_V1_1 = android::hardware::gnss::V1_1::IGnssConfiguration;
 using IGnssConfiguration_V2_0 = android::hardware::gnss::V2_0::IGnssConfiguration;
+using IGnssConfiguration_V2_1 = android::hardware::gnss::V2_1::IGnssConfiguration;
 using IGnssDebug_V1_0 = android::hardware::gnss::V1_0::IGnssDebug;
 using IGnssDebug_V2_0 = android::hardware::gnss::V2_0::IGnssDebug;
 using IGnssMeasurement_V1_0 = android::hardware::gnss::V1_0::IGnssMeasurement;
@@ -221,6 +223,7 @@
 sp<IGnssConfiguration_V1_0> gnssConfigurationIface = nullptr;
 sp<IGnssConfiguration_V1_1> gnssConfigurationIface_V1_1 = nullptr;
 sp<IGnssConfiguration_V2_0> gnssConfigurationIface_V2_0 = nullptr;
+sp<IGnssConfiguration_V2_1> gnssConfigurationIface_V2_1 = nullptr;
 sp<IGnssNi> gnssNiIface = nullptr;
 sp<IGnssMeasurement_V1_0> gnssMeasurementIface = nullptr;
 sp<IGnssMeasurement_V1_1> gnssMeasurementIface_V1_1 = nullptr;
@@ -1888,7 +1891,17 @@
         gnssNiIface = gnssNi;
     }
 
-    if (gnssHal_V2_0 != nullptr) {
+    if (gnssHal_V2_1 != nullptr) {
+        auto gnssConfiguration = gnssHal_V2_1->getExtensionGnssConfiguration_2_1();
+        if (!gnssConfiguration.isOk()) {
+            ALOGD("Unable to get a handle to GnssConfiguration_V2_1");
+        } else {
+            gnssConfigurationIface_V2_1 = gnssConfiguration;
+            gnssConfigurationIface_V2_0 = gnssConfigurationIface_V2_1;
+            gnssConfigurationIface_V1_1 = gnssConfigurationIface_V2_1;
+            gnssConfigurationIface = gnssConfigurationIface_V2_1;
+        }
+    } else if (gnssHal_V2_0 != nullptr) {
         auto gnssConfiguration = gnssHal_V2_0->getExtensionGnssConfiguration_2_0();
         if (!gnssConfiguration.isOk()) {
             ALOGD("Unable to get a handle to GnssConfiguration_V2_0");
@@ -1962,7 +1975,11 @@
 static jobject android_location_GnssConfiguration_get_gnss_configuration_version(
         JNIEnv* env, jclass /* jclazz */) {
     jint major, minor;
-    if (gnssConfigurationIface_V2_0 != nullptr) {
+    if (gnssConfigurationIface_V2_1 != nullptr) {
+        major = 2;
+        minor = 1;
+    }
+    else if (gnssConfigurationIface_V2_0 != nullptr) {
         major = 2;
         minor = 0;
     } else if (gnssConfigurationIface_V1_1 != nullptr) {
@@ -2768,7 +2785,7 @@
 
         SingleSatCorrection singleSatCorrection = {
             .singleSatCorrectionFlags = corrFlags,
-            .constellation = static_cast<GnssConstellationType>(constType),
+            .constellation = static_cast<GnssConstellationType_V1_0>(constType),
             .svid = static_cast<uint16_t>(satId),
             .carrierFrequencyHz = carrierFreqHz,
             .probSatIsLos = probSatIsLos,
@@ -2863,8 +2880,8 @@
 static jboolean android_location_GnssConfiguration_set_supl_es(JNIEnv*,
                                                                jobject,
                                                                jint suplEs) {
-    if (gnssConfigurationIface_V2_0 != nullptr) {
-        ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration.hal version 2.0.");
+    if (gnssConfigurationIface_V2_0 != nullptr || gnssConfigurationIface_V2_1 != nullptr) {
+        ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration.hal version 2.0 and higher.");
         return JNI_FALSE;
     }
 
@@ -2892,7 +2909,7 @@
 static jboolean android_location_GnssConfiguration_set_gps_lock(JNIEnv*,
                                                                 jobject,
                                                                 jint gpsLock) {
-    if (gnssConfigurationIface_V2_0 != nullptr) {
+    if (gnssConfigurationIface_V2_0 != nullptr || gnssConfigurationIface_V2_1 != nullptr) {
         ALOGI("Config parameter GPS_LOCK is deprecated in IGnssConfiguration.hal version 2.0.");
         return JNI_FALSE;
     }
@@ -2932,7 +2949,7 @@
 
 static jboolean android_location_GnssConfiguration_set_satellite_blacklist(
         JNIEnv* env, jobject, jintArray constellations, jintArray sv_ids) {
-    if (gnssConfigurationIface_V1_1 == nullptr) {
+    if (gnssConfigurationIface_V1_1 == nullptr && gnssConfigurationIface_V2_1 == nullptr) {
         ALOGI("IGnssConfiguration interface does not support satellite blacklist.");
         return JNI_FALSE;
     }
@@ -2955,11 +2972,24 @@
         return JNI_FALSE;
     }
 
+    if (gnssConfigurationIface_V2_1 != nullptr) {
+        hidl_vec<IGnssConfiguration_V2_1::BlacklistedSource> sources;
+        sources.resize(length);
+
+        for (int i = 0; i < length; i++) {
+            sources[i].constellation = static_cast<GnssConstellationType_V2_0>(constellation_array[i]);
+            sources[i].svid = sv_id_array[i];
+        }
+
+        auto result = gnssConfigurationIface_V2_1->setBlacklist_2_1(sources);
+        return checkHidlReturn(result, "IGnssConfiguration_V2_1 setBlacklist_2_1() failed.");
+    }
+
     hidl_vec<IGnssConfiguration_V1_1::BlacklistedSource> sources;
     sources.resize(length);
 
     for (int i = 0; i < length; i++) {
-        sources[i].constellation = static_cast<GnssConstellationType>(constellation_array[i]);
+        sources[i].constellation = static_cast<GnssConstellationType_V1_0>(constellation_array[i]);
         sources[i].svid = sv_id_array[i];
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d5ff280..3592e5f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -12212,6 +12212,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/net/java/android/net/ip/IpClientCallbacks.java b/services/net/java/android/net/ip/IpClientCallbacks.java
index 61cd88a..c93e5c5 100644
--- a/services/net/java/android/net/ip/IpClientCallbacks.java
+++ b/services/net/java/android/net/ip/IpClientCallbacks.java
@@ -17,6 +17,7 @@
 package android.net.ip;
 
 import android.net.DhcpResults;
+import android.net.DhcpResultsParcelable;
 import android.net.Layer2PacketParcelable;
 import android.net.LinkProperties;
 
@@ -69,6 +70,18 @@
     public void onNewDhcpResults(DhcpResults dhcpResults) {}
 
     /**
+     * Callback called when new DHCP results are available.
+     *
+     * <p>This is purely advisory and not an indication of provisioning success or failure.  This is
+     * only here for callers that want to expose DHCPv4 results to other APIs
+     * (e.g., WifiInfo#setInetAddress).
+     *
+     * <p>DHCPv4 or static IPv4 configuration failure or success can be determined by whether or not
+     * the passed-in DhcpResults object is null.
+     */
+    public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {}
+
+    /**
      * Indicates that provisioning was successful.
      */
     public void onProvisioningSuccess(LinkProperties newLp) {}
diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java
index 4d60e62..7f723b1 100644
--- a/services/net/java/android/net/ip/IpClientUtil.java
+++ b/services/net/java/android/net/ip/IpClientUtil.java
@@ -119,6 +119,7 @@
         @Override
         public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {
             mCb.onNewDhcpResults(fromStableParcelable(dhcpResults));
+            mCb.onNewDhcpResults(dhcpResults);
         }
 
         @Override
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 8632ca4..8b2f15c 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -81,7 +81,10 @@
 import org.robolectric.shadows.ShadowPackageManager;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.List;
 
 /**
@@ -1238,13 +1241,49 @@
         assertThat(service.getAncestralSerialNumber()).isEqualTo(testSerialNumber2);
     }
 
+    /**
+     * Test that {@link UserBackupManagerService#dump()} for system user does not prefix dump with
+     * "User 0:".
+     */
+    @Test
+    public void testDump_forSystemUser_DoesNotHaveUserPrefix() throws Exception {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        UserBackupManagerService service =
+                BackupManagerServiceTestUtils.createUserBackupManagerServiceAndRunTasks(
+                        UserHandle.USER_SYSTEM,
+                        mContext,
+                        mBackupThread,
+                        mBaseStateDir,
+                        mDataDir,
+                        mTransportManager);
+
+        StringWriter dump = new StringWriter();
+        service.dump(new FileDescriptor(), new PrintWriter(dump), new String[0]);
+
+        assertThat(dump.toString()).startsWith("Backup Manager is ");
+    }
+
+    /**
+     * Test that {@link UserBackupManagerService#dump()} for non-system user prefixes dump with
+     * "User <userid>:".
+     */
+    @Test
+    public void testDump_forNonSystemUser_HasUserPrefix() throws Exception {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        UserBackupManagerService service = createUserBackupManagerServiceAndRunTasks();
+
+        StringWriter dump = new StringWriter();
+        service.dump(new FileDescriptor(), new PrintWriter(dump), new String[0]);
+
+        assertThat(dump.toString()).startsWith("User " + USER_ID + ":" + "Backup Manager is ");
+    }
+
     private File createTestFile() throws IOException {
         File testFile = new File(mContext.getFilesDir(), "test");
         testFile.createNewFile();
         return testFile;
     }
 
-
     /**
      * We can't mock the void method {@link #schedule(Context, long, BackupManagerConstants)} so we
      * extend {@link ShadowKeyValueBackupJob} and throw an exception at the end of the method.
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java
new file mode 100644
index 0000000..06fb102
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.location;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UserInfoStoreTest {
+
+    private static final int USER1_ID = 1;
+    private static final int USER1_MANAGED_ID = 11;
+    private static final int[] USER1_PROFILES = new int[]{USER1_ID, USER1_MANAGED_ID};
+    private static final int USER2_ID = 2;
+    private static final int USER2_MANAGED_ID = 12;
+    private static final int[] USER2_PROFILES = new int[]{USER2_ID, USER2_MANAGED_ID};
+
+    @Mock private Context mContext;
+    @Mock private UserManager mUserManager;
+
+    private StaticMockitoSession mMockingSession;
+    private List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>();
+
+    private UserInfoStore mStore;
+
+    @Before
+    public void setUp() {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .spyStatic(ActivityManager.class)
+                .strictness(Strictness.WARN)
+                .startMocking();
+
+        doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
+        doAnswer(invocation -> {
+            mBroadcastReceivers.add(invocation.getArgument(0));
+            return null;
+        }).when(mContext).registerReceiverAsUser(any(BroadcastReceiver.class), any(
+                UserHandle.class), any(IntentFilter.class), isNull(), any(Handler.class));
+        doReturn(USER1_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER1_ID);
+        doReturn(USER2_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER2_ID);
+        doReturn(new UserInfo(USER1_ID, "", 0)).when(mUserManager).getProfileParent(
+                USER1_MANAGED_ID);
+        doReturn(new UserInfo(USER2_ID, "", 0)).when(mUserManager).getProfileParent(
+                USER2_MANAGED_ID);
+
+        doReturn(USER1_ID).when(ActivityManager::getCurrentUser);
+
+        mStore = new UserInfoStore(mContext);
+        mStore.onSystemReady();
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    private void switchUser(int userId) {
+        doReturn(userId).when(ActivityManager::getCurrentUser);
+        Intent intent = new Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE,
+                userId);
+        for (BroadcastReceiver broadcastReceiver : mBroadcastReceivers) {
+            broadcastReceiver.onReceive(mContext, intent);
+        }
+    }
+
+    @Test
+    public void testListeners() {
+        UserInfoStore.UserChangedListener listener = mock(UserInfoStore.UserChangedListener.class);
+        mStore.addListener(listener);
+
+        switchUser(USER1_ID);
+        verify(listener, never()).onUserChanged(anyInt(), anyInt());
+
+        switchUser(USER2_ID);
+        verify(listener).onUserChanged(USER1_ID, USER2_ID);
+
+        switchUser(USER1_ID);
+        verify(listener).onUserChanged(USER2_ID, USER1_ID);
+    }
+
+    @Test
+    public void testCurrentUser() {
+        assertThat(mStore.getCurrentUserId()).isEqualTo(USER1_ID);
+
+        switchUser(USER2_ID);
+
+        assertThat(mStore.getCurrentUserId()).isEqualTo(USER2_ID);
+
+        switchUser(USER1_ID);
+
+        assertThat(mStore.getCurrentUserId()).isEqualTo(USER1_ID);
+    }
+
+    @Test
+    public void testIsCurrentUserOrProfile() {
+        assertThat(mStore.isCurrentUserOrProfile(USER1_ID)).isTrue();
+        assertThat(mStore.isCurrentUserOrProfile(USER1_MANAGED_ID)).isTrue();
+        assertThat(mStore.isCurrentUserOrProfile(USER2_ID)).isFalse();
+        assertThat(mStore.isCurrentUserOrProfile(USER2_MANAGED_ID)).isFalse();
+
+        switchUser(USER2_ID);
+
+        assertThat(mStore.isCurrentUserOrProfile(USER1_ID)).isFalse();
+        assertThat(mStore.isCurrentUserOrProfile(USER2_ID)).isTrue();
+        assertThat(mStore.isCurrentUserOrProfile(USER1_MANAGED_ID)).isFalse();
+        assertThat(mStore.isCurrentUserOrProfile(USER2_MANAGED_ID)).isTrue();
+    }
+
+    @Test
+    public void testGetParentUserId() {
+        assertThat(mStore.getParentUserId(USER1_ID)).isEqualTo(USER1_ID);
+        assertThat(mStore.getParentUserId(USER1_MANAGED_ID)).isEqualTo(USER1_ID);
+        assertThat(mStore.getParentUserId(USER2_ID)).isEqualTo(USER2_ID);
+        assertThat(mStore.getParentUserId(USER2_MANAGED_ID)).isEqualTo(USER2_ID);
+
+        switchUser(USER2_ID);
+
+        assertThat(mStore.getParentUserId(USER1_ID)).isEqualTo(USER1_ID);
+        assertThat(mStore.getParentUserId(USER2_ID)).isEqualTo(USER2_ID);
+        assertThat(mStore.getParentUserId(USER1_MANAGED_ID)).isEqualTo(USER1_ID);
+        assertThat(mStore.getParentUserId(USER2_MANAGED_ID)).isEqualTo(USER2_ID);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 1829fb7..2fb2021 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
@@ -56,7 +57,6 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.net.wifi.WifiSsid;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -182,8 +182,8 @@
     }
 
     private ScanResult createScanResult(String ssid, String bssid) {
-        ScanResult result = new ScanResult();
-        result.wifiSsid = WifiSsid.createFromAsciiEncoded(ssid);
+        ScanResult result = mock(ScanResult.class);
+        result.SSID = ssid;
         result.BSSID = bssid;
         return result;
     }
@@ -794,7 +794,7 @@
     @Test
     public void testScanResultsScoreCacheFilter_invalidScanResults() throws Exception {
         List<ScanResult> invalidScanResults = Lists.newArrayList(
-                new ScanResult(),
+                mock(ScanResult.class),
                 createScanResult("", SCORED_NETWORK.networkKey.wifiKey.bssid),
                 createScanResult(WifiManager.UNKNOWN_SSID, SCORED_NETWORK.networkKey.wifiKey.bssid),
                 createScanResult(SSID, null),
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 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 2326dfd..3de006c 100644
--- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
@@ -56,9 +57,9 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -83,6 +84,8 @@
     @Mock
     private UserBackupManagerService mUserBackupManagerService;
     @Mock
+    private UserBackupManagerService mNonSystemUserBackupManagerService;
+    @Mock
     private Context mContextMock;
     @Mock
     private PrintWriter mPrintWriterMock;
@@ -105,7 +108,7 @@
 
         mUserServices = new SparseArray<>();
         mUserServices.append(UserHandle.USER_SYSTEM, mUserBackupManagerService);
-        mUserServices.append(NON_USER_SYSTEM, mUserBackupManagerService);
+        mUserServices.append(NON_USER_SYSTEM, mNonSystemUserBackupManagerService);
 
         when(mUserManagerMock.getUserInfo(UserHandle.USER_SYSTEM)).thenReturn(mUserInfoMock);
         when(mUserManagerMock.getUserInfo(NON_USER_SYSTEM)).thenReturn(mUserInfoMock);
@@ -512,19 +515,53 @@
         mService.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]);
 
         verifyNoMoreInteractions(mUserBackupManagerService);
+        verifyNoMoreInteractions(mNonSystemUserBackupManagerService);
+    }
+
+    /**
+     * Test that {@link BackupManagerService#dump()} dumps system user information before non-system
+     * user information.
+     */
+    @Test
+    public void testDump_systemUserFirst() {
+        String[] args = new String[0];
+        mService.dumpWithoutCheckingPermission(mFileDescriptorStub, mPrintWriterMock, args);
+
+        InOrder inOrder =
+                inOrder(mUserBackupManagerService, mNonSystemUserBackupManagerService);
+        inOrder.verify(mUserBackupManagerService)
+                .dump(mFileDescriptorStub, mPrintWriterMock, args);
+        inOrder.verify(mNonSystemUserBackupManagerService)
+                .dump(mFileDescriptorStub, mPrintWriterMock, args);
+        inOrder.verifyNoMoreInteractions();
     }
 
     @Test
-    @Ignore("b/147012496")
-    public void testGetUserForAncestralSerialNumber() {
+    public void testGetUserForAncestralSerialNumber_forSystemUser() {
         BackupManagerServiceTestable.sBackupDisabled = false;
         BackupManagerService backupManagerService =
                 new BackupManagerServiceTestable(mContextMock, mUserServices);
+        when(mUserManagerMock.getProfileIds(UserHandle.getCallingUserId(), false))
+                .thenReturn(new int[] {UserHandle.USER_SYSTEM, NON_USER_SYSTEM});
         when(mUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L);
 
         UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L);
 
-        assertThat(user).isEqualTo(UserHandle.of(1));
+        assertThat(user).isEqualTo(UserHandle.of(UserHandle.USER_SYSTEM));
+    }
+
+    @Test
+    public void testGetUserForAncestralSerialNumber_forNonSystemUser() {
+        BackupManagerServiceTestable.sBackupDisabled = false;
+        BackupManagerService backupManagerService =
+                new BackupManagerServiceTestable(mContextMock, mUserServices);
+        when(mUserManagerMock.getProfileIds(UserHandle.getCallingUserId(), false))
+                .thenReturn(new int[] {UserHandle.USER_SYSTEM, NON_USER_SYSTEM});
+        when(mNonSystemUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L);
+
+        UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L);
+
+        assertThat(user).isEqualTo(UserHandle.of(NON_USER_SYSTEM));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 175c756..20cb449 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -2845,6 +2845,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 +2857,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 +2885,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 +2894,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 +2906,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 +2920,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 +2947,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 +2961,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 +3007,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 +3022,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 +3040,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 +3172,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 +3187,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 +3215,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 +3231,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 +3258,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 +3271,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 +3300,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 +3315,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/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/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index 821d97a..670bd81 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -67,13 +67,25 @@
 public class PlatformKeyManagerTest {
 
     private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
-    private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
+    private static final int MIN_GENERATION_ID = 1000000;
+    private static final int PRIMARY_USER_ID_FIXTURE = 0;
     private static final int USER_ID_FIXTURE = 42;
     private static final long USER_SID = 4200L;
     private static final String KEY_ALGORITHM = "AES";
     private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
     private static final String TESTING_KEYSTORE_KEY_ALIAS = "testing-key-store-key-alias";
 
+    private static final String ENCRYPTION_KEY_ALIAS_1 =
+             "com.android.server.locksettings.recoverablekeystore/platform/42/1000000/encrypt";
+    private static final String DECRYPTION_KEY_ALIAS_1 =
+             "com.android.server.locksettings.recoverablekeystore/platform/42/1000000/decrypt";
+    private static final String DECRYPTION_KEY_FOR_ALIAS_PRIMARY_USER_1 =
+             "com.android.server.locksettings.recoverablekeystore/platform/0/1000000/decrypt";
+    private static final String ENCRYPTION_KEY_ALIAS_2 =
+             "com.android.server.locksettings.recoverablekeystore/platform/42/1000001/encrypt";
+    private static final String DECRYPTION_KEY_ALIAS_2 =
+             "com.android.server.locksettings.recoverablekeystore/platform/42/1000001/decrypt";
+
     @Mock private Context mContext;
     @Mock private KeyStoreProxy mKeyStoreProxy;
     @Mock private KeyguardManager mKeyguardManager;
@@ -114,7 +126,7 @@
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_1),
                 any(),
                 any());
     }
@@ -156,7 +168,7 @@
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any(),
                 any());
     }
@@ -187,19 +199,33 @@
     }
 
     @Test
-    public void init_createsDecryptKeyWithAuthenticationRequired() throws Exception {
-        mPlatformKeyManager.init(USER_ID_FIXTURE);
+    public void init_primaryUser_createsDecryptKeyWithUnlockedDeviceRequired() throws Exception {
+        mPlatformKeyManager.init(PRIMARY_USER_ID_FIXTURE);
 
-        assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired());
+        assertTrue(getDecryptKeyProtectionForPrimaryUser().isUnlockedDeviceRequired());
     }
 
     @Test
-    public void init_createsDecryptKeyWithAuthenticationValidFor15Seconds() throws Exception {
+    public void init_primaryUser_createsDecryptKeyWithoutAuthenticationRequired() throws Exception {
+        mPlatformKeyManager.init(PRIMARY_USER_ID_FIXTURE);
+
+        assertFalse(getDecryptKeyProtectionForPrimaryUser().isUserAuthenticationRequired());
+    }
+
+    @Test
+    public void init_secondaryUser_createsDecryptKeyWithoutUnlockedDeviceRequired()
+            throws Exception {
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        assertEquals(
-                USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS,
-                getDecryptKeyProtection().getUserAuthenticationValidityDurationSeconds());
+        assertFalse(getDecryptKeyProtection().isUnlockedDeviceRequired());
+    }
+
+    @Test
+    public void init_secondaryUserUser_createsDecryptKeyWithAuthenticationRequired()
+            throws Exception {
+        mPlatformKeyManager.init(USER_ID_FIXTURE);
+
+        assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired());
     }
 
     @Test
@@ -219,7 +245,7 @@
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy, never()).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any(),
                 any());
     }
@@ -231,7 +257,7 @@
         expectThrows(RemoteException.class, () -> mPlatformKeyManager.init(USER_ID_FIXTURE));
 
         verify(mKeyStoreProxy, never()).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any(),
                 any());
     }
@@ -251,15 +277,15 @@
     public void init_savesGenerationIdToDatabase() throws Exception {
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        assertEquals(1,
+        assertEquals(MIN_GENERATION_ID,
                 mRecoverableKeyStoreDb.getPlatformKeyGenerationId(USER_ID_FIXTURE));
     }
 
     @Test
-    public void init_setsGenerationIdTo1() throws Exception {
+    public void init_setsGenerationId() throws Exception {
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+        assertEquals(MIN_GENERATION_ID, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
     }
 
     @Test
@@ -268,22 +294,20 @@
 
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+        assertEquals(MIN_GENERATION_ID + 1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
     }
 
     @Test
     public void init_doesNotIncrementGenerationIdIfKeyAvailable() throws Exception {
         mPlatformKeyManager.init(USER_ID_FIXTURE);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
 
         mPlatformKeyManager.init(USER_ID_FIXTURE);
 
-        assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+        assertEquals(MIN_GENERATION_ID, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
     }
 
     @Test
@@ -294,226 +318,194 @@
     @Test
     public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy.getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any())).thenReturn(generateAndroidKeyStoreKey());
 
         mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any());
     }
 
     @Test
     public void getDecryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(false); // was removed
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
         when(mKeyStoreProxy.getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any())).thenReturn(generateAndroidKeyStoreKey());
 
         mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).containsAlias(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"));
+                eq(DECRYPTION_KEY_ALIAS_1));
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getDecryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(false); // was removed
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).containsAlias(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+                eq(ENCRYPTION_KEY_ALIAS_1));
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getEncryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
         doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_1),
                 any());
 
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_1),
                 any());
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getDecryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
         doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any());
         when(mKeyStoreProxy.getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any())).thenReturn(generateAndroidKeyStoreKey());
 
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).containsAlias(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"));
+                eq(DECRYPTION_KEY_ALIAS_1));
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getEncryptKey_generatesNewKeyIfDecryptKeyIsUnrecoverable() throws Exception {
         doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
                 any());
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(false); // was removed
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getEncryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(false); // was removed
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).containsAlias(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+                eq(ENCRYPTION_KEY_ALIAS_1));
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getEncryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(false); // was removed
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).containsAlias(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+                eq(ENCRYPTION_KEY_ALIAS_1));
         // Attempt to get regenerated key.
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_2),
                 any());
     }
 
     @Test
     public void getEncryptKey_getsEncryptKeyWithCorrectAlias() throws Exception {
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(true);
+                .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
         when(mKeyStoreProxy
-                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(true);
+                .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).getKey(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_1),
                 any());
     }
 
@@ -523,7 +515,7 @@
 
         mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
-        assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+        assertEquals(MIN_GENERATION_ID + 1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
     }
 
     @Test
@@ -533,17 +525,17 @@
         mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).deleteEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+                eq(ENCRYPTION_KEY_ALIAS_1));
         verify(mKeyStoreProxy).deleteEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"));
+                eq(DECRYPTION_KEY_ALIAS_1));
 
         mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
         // Removes second generation keys.
         verify(mKeyStoreProxy).deleteEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"));
+                eq(ENCRYPTION_KEY_ALIAS_2));
         verify(mKeyStoreProxy).deleteEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"));
+                eq(DECRYPTION_KEY_ALIAS_2));
     }
 
     @Test
@@ -553,7 +545,7 @@
         mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_2),
                 any(),
                 any());
     }
@@ -565,14 +557,14 @@
         mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
 
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_2),
                 any(),
                 any());
     }
 
     private KeyProtection getEncryptKeyProtection() throws Exception {
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+                eq(ENCRYPTION_KEY_ALIAS_1),
                 any(),
                 mProtectionParameterCaptor.capture());
         return (KeyProtection) mProtectionParameterCaptor.getValue();
@@ -580,7 +572,15 @@
 
     private KeyProtection getDecryptKeyProtection() throws Exception {
         verify(mKeyStoreProxy).setEntry(
-                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                eq(DECRYPTION_KEY_ALIAS_1),
+                any(),
+                mProtectionParameterCaptor.capture());
+        return (KeyProtection) mProtectionParameterCaptor.getValue();
+    }
+
+    private KeyProtection getDecryptKeyProtectionForPrimaryUser() throws Exception {
+        verify(mKeyStoreProxy).setEntry(
+                eq(DECRYPTION_KEY_FOR_ALIAS_PRIMARY_USER_1),
                 any(),
                 mProtectionParameterCaptor.capture());
         return (KeyProtection) mProtectionParameterCaptor.getValue();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 6890017..ac74470 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -29,6 +29,7 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeast;
@@ -84,7 +85,7 @@
 import java.util.ArrayList;
 import java.util.Map;
 import java.util.Random;
-import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
 
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
@@ -157,7 +158,7 @@
     @Mock private PlatformKeyManager mPlatformKeyManager;
     @Mock private ApplicationKeyStorage mApplicationKeyStorage;
     @Mock private CleanupManager mCleanupManager;
-    @Mock private ExecutorService mExecutorService;
+    @Mock private ScheduledExecutorService mExecutorService;
     @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
 
     private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
@@ -1253,7 +1254,7 @@
         mRecoverableKeyStoreManager.lockScreenSecretAvailable(
                 LockPatternUtils.CREDENTIAL_TYPE_PATTERN, "password".getBytes(), 11);
 
-        verify(mExecutorService).execute(any());
+        verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any());
     }
 
     @Test
@@ -1263,7 +1264,7 @@
                 "password".getBytes(),
                 11);
 
-        verify(mExecutorService).execute(any());
+        verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any());
     }
 
     private static byte[] encryptedApplicationKey(
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index f8915c0..cc170af 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -37,6 +37,7 @@
 import android.hardware.soundtrigger.V2_3.OptionalModelParameterRange;
 import android.media.audio.common.AudioChannelMask;
 import android.media.audio.common.AudioFormat;
+import android.media.soundtrigger_middleware.AudioCapabilities;
 import android.media.soundtrigger_middleware.ConfidenceLevel;
 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
 import android.media.soundtrigger_middleware.ISoundTriggerModule;
@@ -59,6 +60,7 @@
 import android.os.IHwBinder;
 import android.os.IHwInterface;
 import android.os.RemoteException;
+import android.util.Pair;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -155,7 +157,19 @@
         return properties;
     }
 
-    private static void validateDefaultProperties(SoundTriggerModuleProperties properties,
+    private static android.hardware.soundtrigger.V2_3.Properties createDefaultProperties_2_3(
+            boolean supportConcurrentCapture) {
+        android.hardware.soundtrigger.V2_3.Properties properties =
+                new android.hardware.soundtrigger.V2_3.Properties();
+        properties.base = createDefaultProperties(supportConcurrentCapture);
+        properties.supportedModelArch = "supportedModelArch";
+        properties.audioCapabilities =
+                android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION
+                        | android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION;
+        return properties;
+    }
+
+    private void validateDefaultProperties(SoundTriggerModuleProperties properties,
             boolean supportConcurrentCapture) {
         assertEquals("implementor", properties.implementor);
         assertEquals("description", properties.description);
@@ -173,8 +187,23 @@
         assertEquals(supportConcurrentCapture, properties.concurrentCapture);
         assertTrue(properties.triggerInEvent);
         assertEquals(432, properties.powerConsumptionMw);
+
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            assertEquals("supportedModelArch", properties.supportedModelArch);
+            assertEquals(AudioCapabilities.ECHO_CANCELLATION | AudioCapabilities.NOISE_SUPPRESSION,
+                    properties.audioCapabilities);
+        } else {
+            assertEquals("", properties.supportedModelArch);
+            assertEquals(0, properties.audioCapabilities);
+        }
     }
 
+    private void verifyNotGetProperties() throws RemoteException {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver,
+                    never()).getProperties(any());
+        }
+    }
 
     private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_0(
             int hwHandle,
@@ -290,15 +319,36 @@
                     properties);
             return null;
         }).when(mHalDriver).getProperties(any());
+
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+                    (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+            doAnswer(invocation -> {
+                android.hardware.soundtrigger.V2_3.Properties properties =
+                        createDefaultProperties_2_3(
+                                supportConcurrentCapture);
+                ((android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getProperties_2_3Callback)
+                        invocation.getArgument(
+                        0)).onValues(0,
+                        properties);
+                return null;
+            }).when(driver).getProperties_2_3(any());
+        }
+
         mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider);
     }
 
-    private int loadGenericModel_2_0(ISoundTriggerModule module, int hwHandle)
-            throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_0(ISoundTriggerModule module,
+            int hwHandle) throws RemoteException {
         SoundModel model = createGenericSoundModel();
         ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel> modelCaptor =
                 ArgumentCaptor.forClass(
                         android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
                     invocation.getArgument(1);
@@ -317,7 +367,8 @@
             modelEvent.model = hwHandle;
             callback.soundModelCallback(modelEvent, callbackCookie);
             return null;
-        }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+        }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), callbackCaptor.capture(),
+                cookieCaptor.capture(), any());
 
         when(mAudioSessionProvider.acquireSession()).thenReturn(
                 new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -334,17 +385,23 @@
         assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.vendorUuid));
         assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.data.toArray());
 
-        return handle;
+        return new Pair<>(handle,
+                new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
     }
 
-    private int loadGenericModel_2_1(ISoundTriggerModule module, int hwHandle)
-            throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_1(ISoundTriggerModule module,
+            int hwHandle) throws RemoteException {
         android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
                 (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
         SoundModel model = createGenericSoundModel();
         ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel> modelCaptor =
                 ArgumentCaptor.forClass(
                         android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
                     invocation.getArgument(1);
@@ -363,7 +420,8 @@
             modelEvent.header.model = hwHandle;
             callback.soundModelCallback_2_1(modelEvent, callbackCookie);
             return null;
-        }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+        }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(),
+                cookieCaptor.capture(), any());
 
         when(mAudioSessionProvider.acquireSession()).thenReturn(
                 new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -381,10 +439,12 @@
         assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
                 HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.data));
 
-        return handle;
+        return new Pair<>(handle,
+                new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
     }
 
-    private int loadGenericModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadGenericModel(ISoundTriggerModule module,
+            int hwHandle) throws RemoteException {
         if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
             return loadGenericModel_2_1(module, hwHandle);
         } else {
@@ -392,12 +452,17 @@
         }
     }
 
-    private int loadPhraseModel_2_0(ISoundTriggerModule module, int hwHandle)
-            throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_0(ISoundTriggerModule module,
+            int hwHandle) throws RemoteException {
         PhraseSoundModel model = createPhraseSoundModel();
         ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel>
                 modelCaptor = ArgumentCaptor.forClass(
                 android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
                     invocation.getArgument(
@@ -419,7 +484,8 @@
             modelEvent.model = hwHandle;
             callback.soundModelCallback(modelEvent, callbackCookie);
             return null;
-        }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+        }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), callbackCaptor.capture(),
+                cookieCaptor.capture(), any());
 
         when(mAudioSessionProvider.acquireSession()).thenReturn(
                 new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -451,11 +517,12 @@
                         | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
                 hidlPhrase.recognitionModes);
 
-        return handle;
+        return new Pair<>(handle,
+                new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
     }
 
-    private int loadPhraseModel_2_1(ISoundTriggerModule module, int hwHandle)
-            throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_1(ISoundTriggerModule module,
+            int hwHandle) throws RemoteException {
         android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
                 (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
 
@@ -463,6 +530,11 @@
         ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel>
                 modelCaptor = ArgumentCaptor.forClass(
                 android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel.class);
+        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+                ArgumentCaptor.forClass(
+                        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
                     invocation.getArgument(
@@ -484,7 +556,8 @@
             modelEvent.header.model = hwHandle;
             callback.soundModelCallback_2_1(modelEvent, callbackCookie);
             return null;
-        }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+        }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(),
+                cookieCaptor.capture(), any());
 
         when(mAudioSessionProvider.acquireSession()).thenReturn(
                 new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -517,10 +590,12 @@
                         | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
                 hidlPhrase.recognitionModes);
 
-        return handle;
+        return new Pair<>(handle,
+                new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
     }
 
-    private int loadPhraseModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+    private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel(
+            ISoundTriggerModule module, int hwHandle) throws RemoteException {
         if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
             return loadPhraseModel_2_1(module, hwHandle);
         } else {
@@ -535,18 +610,14 @@
         verify(mAudioSessionProvider).releaseSession(101);
     }
 
-    private SoundTriggerHwCallback startRecognition_2_0(ISoundTriggerModule module, int handle,
+    private void startRecognition_2_0(ISoundTriggerModule module, int handle,
             int hwHandle) throws RemoteException {
         ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig>
                 configCaptor = ArgumentCaptor.forClass(
                 android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig.class);
-        ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
-                ArgumentCaptor.forClass(
-                        android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
-        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
 
-        when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(),
-                callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+        when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(), any(), anyInt()))
+                .thenReturn(0);
 
         RecognitionConfig config = createRecognitionConfig();
 
@@ -569,11 +640,9 @@
         assertEquals(234, halLevel.userId);
         assertEquals(34, halLevel.levelPercent);
         assertArrayEquals(new Byte[]{5, 4, 3, 2, 1}, halConfig.data.toArray());
-
-        return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
     }
 
-    private SoundTriggerHwCallback startRecognition_2_1(ISoundTriggerModule module, int handle,
+    private void startRecognition_2_1(ISoundTriggerModule module, int handle,
             int hwHandle) throws RemoteException {
         android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
                 (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
@@ -581,13 +650,9 @@
         ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig>
                 configCaptor = ArgumentCaptor.forClass(
                 android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig.class);
-        ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
-                ArgumentCaptor.forClass(
-                        android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
-        ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
 
-        when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(),
-                callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+        when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(), any(), anyInt()))
+                .thenReturn(0);
 
         RecognitionConfig config = createRecognitionConfig();
 
@@ -611,16 +676,56 @@
         assertEquals(34, halLevel.levelPercent);
         assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
                 HidlMemoryUtil.hidlMemoryToByteArray(halConfig.data));
-
-        return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
     }
 
-    private SoundTriggerHwCallback startRecognition(ISoundTriggerModule module, int handle,
+    private void startRecognition_2_3(ISoundTriggerModule module, int handle,
             int hwHandle) throws RemoteException {
-        if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
-            return startRecognition_2_1(module, handle, hwHandle);
+        android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+                (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+        ArgumentCaptor<android.hardware.soundtrigger.V2_3.RecognitionConfig>
+                configCaptor = ArgumentCaptor.forClass(
+                android.hardware.soundtrigger.V2_3.RecognitionConfig.class);
+
+        when(driver.startRecognition_2_3(eq(hwHandle), configCaptor.capture())).thenReturn(0);
+
+        RecognitionConfig config = createRecognitionConfig();
+
+        module.startRecognition(handle, config);
+        verify(driver).startRecognition_2_3(eq(hwHandle), any());
+
+        android.hardware.soundtrigger.V2_3.RecognitionConfig halConfigExtended =
+                configCaptor.getValue();
+        android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig halConfig_2_1 =
+                halConfigExtended.base;
+
+        assertTrue(halConfig_2_1.header.captureRequested);
+        assertEquals(102, halConfig_2_1.header.captureHandle);
+        assertEquals(103, halConfig_2_1.header.captureDevice);
+        assertEquals(1, halConfig_2_1.header.phrases.size());
+        android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+                halConfig_2_1.header.phrases.get(0);
+        assertEquals(123, halPhraseExtra.id);
+        assertEquals(4, halPhraseExtra.confidenceLevel);
+        assertEquals(5, halPhraseExtra.recognitionModes);
+        assertEquals(1, halPhraseExtra.levels.size());
+        android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0);
+        assertEquals(234, halLevel.userId);
+        assertEquals(34, halLevel.levelPercent);
+        assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
+                HidlMemoryUtil.hidlMemoryToByteArray(halConfig_2_1.data));
+        assertEquals(AudioCapabilities.ECHO_CANCELLATION
+                | AudioCapabilities.NOISE_SUPPRESSION, halConfigExtended.audioCapabilities);
+    }
+
+    private void startRecognition(ISoundTriggerModule module, int handle,
+            int hwHandle) throws RemoteException {
+        if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            startRecognition_2_3(module, handle, hwHandle);
+        } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+            startRecognition_2_1(module, handle, hwHandle);
         } else {
-            return startRecognition_2_0(module, handle, hwHandle);
+            startRecognition_2_0(module, handle, hwHandle);
         }
     }
 
@@ -635,6 +740,8 @@
         config.phraseRecognitionExtras[0].levels[0].userId = 234;
         config.phraseRecognitionExtras[0].levels[0].levelPercent = 34;
         config.data = new byte[]{5, 4, 3, 2, 1};
+        config.audioCapabilities = AudioCapabilities.ECHO_CANCELLATION
+                | AudioCapabilities.NOISE_SUPPRESSION;
         return config;
     }
 
@@ -650,6 +757,9 @@
         if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
             verify((android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver,
                     never()).startRecognition_2_1(anyInt(), any(), any(), anyInt());
+        } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+            verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver,
+                    never()).startRecognition_2_3(anyInt(), any());
         }
     }
 
@@ -716,6 +826,7 @@
         SoundTriggerModuleProperties properties = allDescriptors[0].properties;
 
         validateDefaultProperties(properties, true);
+        verifyNotGetProperties();
     }
 
     @Test
@@ -760,7 +871,7 @@
         ISoundTriggerModule module = mService.attach(0, callback);
 
         final int hwHandle = 7;
-        int handle = loadGenericModel(module, hwHandle);
+        int handle = loadGenericModel(module, hwHandle).first;
         unloadModel(module, handle, hwHandle);
         module.detach();
     }
@@ -772,7 +883,7 @@
         ISoundTriggerModule module = mService.attach(0, callback);
 
         final int hwHandle = 73;
-        int handle = loadPhraseModel(module, hwHandle);
+        int handle = loadPhraseModel(module, hwHandle).first;
         unloadModel(module, handle, hwHandle);
         module.detach();
     }
@@ -785,7 +896,7 @@
 
         // Load the model.
         final int hwHandle = 7;
-        int handle = loadGenericModel(module, hwHandle);
+        int handle = loadGenericModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -806,7 +917,7 @@
 
         // Load the model.
         final int hwHandle = 67;
-        int handle = loadPhraseModel(module, hwHandle);
+        int handle = loadPhraseModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -827,10 +938,12 @@
 
         // Load the model.
         final int hwHandle = 7;
-        int handle = loadGenericModel(module, hwHandle);
+        Pair<Integer, SoundTriggerHwCallback> modelHandles = loadGenericModel(module, hwHandle);
+        int handle = modelHandles.first;
+        SoundTriggerHwCallback hwCallback = modelHandles.second;
 
         // Initiate a recognition.
-        SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+        startRecognition(module, handle, hwHandle);
 
         // Signal a capture from the driver.
         hwCallback.sendRecognitionEvent(hwHandle,
@@ -856,10 +969,12 @@
 
         // Load the model.
         final int hwHandle = 7;
-        int handle = loadPhraseModel(module, hwHandle);
+        Pair<Integer, SoundTriggerHwCallback> modelHandles = loadPhraseModel(module, hwHandle);
+        int handle = modelHandles.first;
+        SoundTriggerHwCallback hwCallback = modelHandles.second;
 
         // Initiate a recognition.
-        SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+        startRecognition(module, handle, hwHandle);
 
         // Signal a capture from the driver.
         hwCallback.sendPhraseRecognitionEvent(hwHandle,
@@ -892,10 +1007,12 @@
 
         // Load the model.
         final int hwHandle = 17;
-        int handle = loadGenericModel(module, hwHandle);
+        Pair<Integer, SoundTriggerHwCallback> modelHandles = loadGenericModel(module, hwHandle);
+        int handle = modelHandles.first;
+        SoundTriggerHwCallback hwCallback = modelHandles.second;
 
         // Initiate a recognition.
-        SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+        startRecognition(module, handle, hwHandle);
 
         // Force a trigger.
         module.forceRecognitionEvent(handle);
@@ -935,10 +1052,12 @@
 
         // Load the model.
         final int hwHandle = 17;
-        int handle = loadPhraseModel(module, hwHandle);
+        Pair<Integer, SoundTriggerHwCallback> modelHandles = loadPhraseModel(module, hwHandle);
+        int handle = modelHandles.first;
+        SoundTriggerHwCallback hwCallback = modelHandles.second;
 
         // Initiate a recognition.
-        SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+        startRecognition(module, handle, hwHandle);
 
         // Force a trigger.
         module.forceRecognitionEvent(handle);
@@ -975,7 +1094,7 @@
 
         // Load the model.
         final int hwHandle = 11;
-        int handle = loadGenericModel(module, hwHandle);
+        int handle = loadGenericModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -1023,7 +1142,7 @@
 
         // Load the model.
         final int hwHandle = 11;
-        int handle = loadPhraseModel(module, hwHandle);
+        int handle = loadPhraseModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -1071,7 +1190,7 @@
 
         // Load the model.
         final int hwHandle = 13;
-        int handle = loadGenericModel(module, hwHandle);
+        int handle = loadGenericModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -1107,7 +1226,7 @@
 
         // Load the model.
         final int hwHandle = 13;
-        int handle = loadPhraseModel(module, hwHandle);
+        int handle = loadPhraseModel(module, hwHandle).first;
 
         // Initiate a recognition.
         startRecognition(module, handle, hwHandle);
@@ -1144,7 +1263,7 @@
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 12;
-        int modelHandle = loadGenericModel(module, hwHandle);
+        int modelHandle = loadGenericModel(module, hwHandle).first;
 
         doAnswer((Answer<Void>) invocation -> {
             android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
@@ -1180,7 +1299,7 @@
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 13;
-        int modelHandle = loadGenericModel(module, hwHandle);
+        int modelHandle = loadGenericModel(module, hwHandle).first;
 
         ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
                 ModelParameter.THRESHOLD_FACTOR);
@@ -1201,7 +1320,7 @@
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 13;
-        int modelHandle = loadGenericModel(module, hwHandle);
+        int modelHandle = loadGenericModel(module, hwHandle).first;
 
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
@@ -1234,7 +1353,7 @@
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 14;
-        int modelHandle = loadGenericModel(module, hwHandle);
+        int modelHandle = loadGenericModel(module, hwHandle).first;
 
         doAnswer(invocation -> {
             android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getParameterCallback
@@ -1266,7 +1385,7 @@
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
         final int hwHandle = 17;
-        int modelHandle = loadGenericModel(module, hwHandle);
+        int modelHandle = loadGenericModel(module, hwHandle).first;
 
         when(driver.setParameter(hwHandle,
                 android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR,
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/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/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index eb45960..bd7d9ec 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -20,7 +20,6 @@
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 
 import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 
 import static org.mockito.Matchers.any;
@@ -65,11 +64,10 @@
 
         NotificationChannel updatedChannel =
                 new NotificationChannel("a", "", IMPORTANCE_HIGH);
-        when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(false)))
+        when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(null), eq(false)))
                 .thenReturn(updatedChannel);
 
         assertNull(extractor.process(r));
         assertEquals(updatedChannel, r.getChannel());
     }
-
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 172df99..62f5230 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -112,6 +112,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -133,6 +134,7 @@
 import android.testing.TestablePermissions;
 import android.testing.TestableResources;
 import android.text.Html;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -475,7 +477,7 @@
         when(mPreferencesHelper.bubblesEnabled()).thenReturn(globalEnabled);
         when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(pkgEnabled);
         when(mPreferencesHelper.getNotificationChannel(
-                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
+                anyString(), anyInt(), anyString(), eq(null), anyBoolean())).thenReturn(
                 mTestNotificationChannel);
         when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
                 mTestNotificationChannel.getImportance());
@@ -1762,7 +1764,7 @@
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0,
                 generateNotificationRecord(null, tv).getNotification(), 0);
         verify(mPreferencesHelper, times(1)).getNotificationChannel(
-                anyString(), anyInt(), eq("foo"), anyBoolean());
+                anyString(), anyInt(), eq("foo"), eq(null), anyBoolean());
     }
 
     @Test
@@ -1777,7 +1779,8 @@
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv",
                 0, generateNotificationRecord(null, tv).getNotification(), 0);
         verify(mPreferencesHelper, times(1)).getNotificationChannel(
-                anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
+                anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null),
+                anyBoolean());
     }
 
     @Test
@@ -3210,9 +3213,11 @@
         final NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 1, 2, true);
         mService.mNotificationDelegate.onNotificationVisibilityChanged(
                 new NotificationVisibility[] {nv}, new NotificationVisibility[]{});
+        verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(true));
         assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen());
         mService.mNotificationDelegate.onNotificationVisibilityChanged(
                 new NotificationVisibility[] {}, new NotificationVisibility[]{nv});
+        verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(false));
         assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen());
     }
 
@@ -4463,6 +4468,16 @@
     }
 
     @Test
+    public void testOnPanelRevealedAndHidden() {
+        int items = 5;
+        mService.mNotificationDelegate.onPanelRevealed(false, items);
+        verify(mAssistants, times(1)).onPanelRevealed(eq(items));
+
+        mService.mNotificationDelegate.onPanelHidden();
+        verify(mAssistants, times(1)).onPanelHidden();
+    }
+
+    @Test
     public void testOnNotificationSmartReplySent() {
         final int replyIndex = 2;
         final String reply = "Hello";
@@ -5044,10 +5059,9 @@
                 nb.build(), new UserHandle(mUid), null, 0);
         // Make sure it has foreground service
         sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
-        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
-                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
         waitForIdle();
 
         // yes phone call, yes person, yes foreground service, but not allowed, no bubble
@@ -5802,4 +5816,79 @@
 
         verify(mHistoryManager, times(1)).addNotification(any());
     }
+
+    @Test
+    public void createConversationNotificationChannel() throws Exception {
+        NotificationChannel original = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+        original.setAllowBubbles(!original.canBubble());
+        original.setShowBadge(!original.canShowBadge());
+
+        Parcel parcel = Parcel.obtain();
+        original.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NotificationChannel orig = NotificationChannel.CREATOR.createFromParcel(parcel);
+        assertEquals(original, orig);
+        assertFalse(TextUtils.isEmpty(orig.getName()));
+
+        mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(
+                orig)));
+
+        mBinderService.createConversationNotificationChannelForPackage(PKG, mUid, orig, "friend");
+
+        NotificationChannel friendChannel = mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, original.getId(), "friend");
+
+        assertEquals(original.getName(), friendChannel.getName());
+        assertEquals(original.getId(), friendChannel.getParentChannelId());
+        assertEquals("friend", friendChannel.getConversationId());
+        assertEquals(null, original.getConversationId());
+        assertEquals(original.canShowBadge(), friendChannel.canShowBadge());
+        assertEquals(original.canBubble(), friendChannel.canBubble());
+        assertFalse(original.getId().equals(friendChannel.getId()));
+        assertNotNull(friendChannel.getId());
+    }
+
+    @Test
+    public void deleteConversationNotificationChannels() throws Exception {
+        NotificationChannel messagesParent =
+                new NotificationChannel("messages", "messages", IMPORTANCE_HIGH);
+        Parcel msgParcel = Parcel.obtain();
+        messagesParent.writeToParcel(msgParcel, 0);
+        msgParcel.setDataPosition(0);
+
+        NotificationChannel callsParent =
+                new NotificationChannel("calls", "calls", IMPORTANCE_HIGH);
+        Parcel callParcel = Parcel.obtain();
+        callsParent.writeToParcel(callParcel, 0);
+        callParcel.setDataPosition(0);
+
+        mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(
+                messagesParent, callsParent)));
+
+        String conversationId = "friend";
+
+        mBinderService.createConversationNotificationChannelForPackage(
+                PKG, mUid, NotificationChannel.CREATOR.createFromParcel(msgParcel), conversationId);
+        mBinderService.createConversationNotificationChannelForPackage(
+                PKG, mUid, NotificationChannel.CREATOR.createFromParcel(callParcel),
+                conversationId);
+
+        NotificationChannel messagesChild = mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, messagesParent.getId(), conversationId);
+        NotificationChannel callsChild = mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, callsParent.getId(), conversationId);
+
+        assertEquals(messagesParent.getId(), messagesChild.getParentChannelId());
+        assertEquals(conversationId, messagesChild.getConversationId());
+
+        assertEquals(callsParent.getId(), callsChild.getParentChannelId());
+        assertEquals(conversationId, callsChild.getConversationId());
+
+        mBinderService.deleteConversationNotificationChannels(PKG, mUid, conversationId);
+
+        assertNull(mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, messagesParent.getId(), conversationId));
+        assertNull(mBinderService.getConversationNotificationChannel(
+                PKG, 0, PKG, callsParent.getId(), conversationId));
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 8961796..7173e0c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.notification;
 
+import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -224,6 +225,26 @@
         assertEquals(expected.getGroup(), actual.getGroup());
         assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
         assertEquals(expected.getLightColor(), actual.getLightColor());
+        assertEquals(expected.getParentChannelId(), actual.getParentChannelId());
+        assertEquals(expected.getConversationId(), actual.getConversationId());
+    }
+
+    private void compareChannelsParentChild(NotificationChannel parent,
+            NotificationChannel actual, String conversationId) {
+        assertEquals(parent.getName(), actual.getName());
+        assertEquals(parent.getDescription(), actual.getDescription());
+        assertEquals(parent.shouldVibrate(), actual.shouldVibrate());
+        assertEquals(parent.shouldShowLights(), actual.shouldShowLights());
+        assertEquals(parent.getImportance(), actual.getImportance());
+        assertEquals(parent.getLockscreenVisibility(), actual.getLockscreenVisibility());
+        assertEquals(parent.getSound(), actual.getSound());
+        assertEquals(parent.canBypassDnd(), actual.canBypassDnd());
+        assertTrue(Arrays.equals(parent.getVibrationPattern(), actual.getVibrationPattern()));
+        assertEquals(parent.getGroup(), actual.getGroup());
+        assertEquals(parent.getAudioAttributes(), actual.getAudioAttributes());
+        assertEquals(parent.getLightColor(), actual.getLightColor());
+        assertEquals(parent.getId(), actual.getParentChannelId());
+        assertEquals(conversationId, actual.getConversationId());
     }
 
     private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
@@ -2786,4 +2807,40 @@
         assertEquals(user10Importance, mHelper.getNotificationChannel(
                 pkg, uidList10[0], channelId, false).getImportance());
     }
+
+    @Test
+    public void testGetConversationNotificationChannel() {
+        String conversationId = "friend";
+
+        NotificationChannel parent =
+                new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
+
+        NotificationChannel friend = new NotificationChannel(String.format(
+                CONVERSATION_CHANNEL_ID_FORMAT, parent.getId(), conversationId),
+                "messages", IMPORTANCE_DEFAULT);
+        friend.setConversationId(parent.getId(), conversationId);
+        mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
+
+        compareChannelsParentChild(parent, mHelper.getNotificationChannel(
+                PKG_O, UID_O, parent.getId(), conversationId, false), conversationId);
+    }
+
+    @Test
+    public void testConversationNotificationChannelsRequireParents() {
+        String parentId = "does not exist";
+        String conversationId = "friend";
+
+        NotificationChannel friend = new NotificationChannel(String.format(
+                CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId),
+                "messages", IMPORTANCE_DEFAULT);
+        friend.setConversationId(parentId, conversationId);
+
+        try {
+            mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
+            fail("allowed creation of conversation channel without a parent");
+        } catch (IllegalArgumentException e) {
+            // good
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 09ac9ce..d819b1a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -63,6 +63,26 @@
         assertEquals(Insets.of(0, 100, 0, 0),
                 mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
                         false /* ignoreVisibility */));
+        assertEquals(Insets.of(0, 100, 0, 0),
+                mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500)));
+    }
+
+    @Test
+    public void testPostLayout_givenInsets() {
+        final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime");
+        ime.getFrameLw().set(0, 0, 500, 100);
+        ime.getGivenContentInsetsLw().set(0, 0, 0, 60);
+        ime.getGivenVisibleInsetsLw().set(0, 0, 0, 75);
+        ime.mHasSurface = true;
+        mProvider.setWindow(ime, null);
+        mProvider.onPostLayout();
+        assertEquals(new Rect(0, 0, 500, 40), mProvider.getSource().getFrame());
+        assertEquals(new Rect(0, 0, 500, 25), mProvider.getSource().getVisibleFrame());
+        assertEquals(Insets.of(0, 40, 0, 0),
+                mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
+                        false /* ignoreVisibility */));
+        assertEquals(Insets.of(0, 25, 0, 0),
+                mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500)));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
new file mode 100644
index 0000000..8d2da1e
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.view.ITaskOrganizer;
+import android.view.SurfaceControl;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link TaskOrganizer}.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:TaskOrganizerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class TaskOrganizerTests extends WindowTestsBase {
+    private ITaskOrganizer makeAndRegisterMockOrganizer() {
+        final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
+        when(organizer.asBinder()).thenReturn(new Binder());
+
+        mWm.mAtmService.registerTaskOrganizer(organizer, WINDOWING_MODE_PINNED);
+
+        return organizer;
+    }
+
+    @Test
+    public void testAppearVanish() throws RemoteException {
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+        task.setTaskOrganizer(organizer);
+        verify(organizer).taskAppeared(any(), any());
+        assertTrue(task.isControlledByTaskOrganizer());
+
+        task.removeImmediately();
+        verify(organizer).taskVanished(any());
+    }
+
+    @Test
+    public void testSwapOrganizer() throws RemoteException {
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+        final ITaskOrganizer organizer2 = makeAndRegisterMockOrganizer();
+
+        task.setTaskOrganizer(organizer);
+        verify(organizer).taskAppeared(any(), any());
+        task.setTaskOrganizer(organizer2);
+        verify(organizer).taskVanished(any());
+        verify(organizer2).taskAppeared(any(), any());
+    }
+
+    @Test
+    public void testClearOrganizer() throws RemoteException {
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+        task.setTaskOrganizer(organizer);
+        verify(organizer).taskAppeared(any(), any());
+        assertTrue(task.isControlledByTaskOrganizer());
+
+        task.setTaskOrganizer(null);
+        verify(organizer).taskVanished(any());
+        assertFalse(task.isControlledByTaskOrganizer());
+    }
+
+    @Test
+    public void testTransferStackToOrganizer() throws RemoteException {
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final Task task2 = createTaskInStack(stack, 0 /* userId */);
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+        stack.transferToTaskOrganizer(organizer);
+
+        verify(organizer, times(2)).taskAppeared(any(), any());
+        assertTrue(task.isControlledByTaskOrganizer());
+        assertTrue(task2.isControlledByTaskOrganizer());
+
+        stack.transferToTaskOrganizer(null);
+
+        verify(organizer, times(2)).taskVanished(any());
+        assertFalse(task.isControlledByTaskOrganizer());
+        assertFalse(task2.isControlledByTaskOrganizer());
+    }
+
+    @Test
+    public void testRegisterTaskOrganizerTaskWindowingModeChanges() throws RemoteException {
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        task.setWindowingMode(WINDOWING_MODE_PINNED);
+        verify(organizer).taskAppeared(any(), any());
+        assertTrue(task.isControlledByTaskOrganizer());
+
+        task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        verify(organizer).taskVanished(any());
+        assertFalse(task.isControlledByTaskOrganizer());
+    }
+
+    @Test
+    public void testRegisterTaskOrganizerStackWindowingModeChanges() throws RemoteException {
+        final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final Task task2 = createTaskInStack(stack, 0 /* userId */);
+        stack.setWindowingMode(WINDOWING_MODE_PINNED);
+        verify(organizer, times(2)).taskAppeared(any(), any());
+        assertTrue(task.isControlledByTaskOrganizer());
+        assertTrue(task2.isControlledByTaskOrganizer());
+
+        stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        verify(organizer, times(2)).taskVanished(any());
+        assertFalse(task.isControlledByTaskOrganizer());
+        assertFalse(task2.isControlledByTaskOrganizer());
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java
deleted file mode 100644
index 9cda084..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.utils;
-
-import static android.graphics.Bitmap.Config.ARGB_8888;
-
-import static org.junit.Assert.assertEquals;
-
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.ColorSpace;
-import android.graphics.GraphicBuffer;
-import android.graphics.Matrix;
-import android.graphics.PointF;
-import android.view.Surface;
-
-import org.junit.Before;
-import org.junit.Test;
-
-public class RotationAnimationUtilsTest {
-
-    private static final int BITMAP_HEIGHT = 100;
-    private static final int BITMAP_WIDTH = 100;
-    private static final int POINT_WIDTH = 1000;
-    private static final int POINT_HEIGHT = 2000;
-
-    private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.DISPLAY_P3);
-    private Matrix mMatrix;
-
-    @Before
-    public void setup() {
-        mMatrix = new Matrix();
-    }
-
-    @Test
-    public void blackLuma() {
-        Bitmap swBitmap = createBitmap(0);
-        GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap);
-        float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace);
-        assertEquals(0, borderLuma, 0);
-    }
-
-    @Test
-    public void whiteLuma() {
-        Bitmap swBitmap = createBitmap(1);
-        GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap);
-        float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace);
-        assertEquals(1, borderLuma, 0);
-    }
-
-    @Test
-    public void whiteImageBlackBorderLuma() {
-        Bitmap swBitmap = createBitmap(1);
-        setBorderLuma(swBitmap, 0);
-        GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap);
-        float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace);
-        assertEquals(0, borderLuma, 0);
-    }
-
-    @Test
-    public void blackImageWhiteBorderLuma() {
-        Bitmap swBitmap = createBitmap(0);
-        setBorderLuma(swBitmap, 1);
-        GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap);
-        float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace);
-        assertEquals(1, borderLuma, 0);
-    }
-
-    @Test
-    public void rotate_0_bottomRight() {
-        RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_0,
-                POINT_WIDTH, POINT_HEIGHT, mMatrix);
-        PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT);
-        assertEquals(POINT_WIDTH, newPoints.x, 0);
-        assertEquals(POINT_HEIGHT, newPoints.y, 0);
-    }
-
-    @Test
-    public void rotate_90_bottomRight() {
-        RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_90,
-                POINT_WIDTH, POINT_HEIGHT, mMatrix);
-        PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT);
-        assertEquals(0, newPoints.x, 0);
-        assertEquals(POINT_WIDTH, newPoints.y, 0);
-    }
-
-    @Test
-    public void rotate_180_bottomRight() {
-        RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_180,
-                POINT_WIDTH, POINT_HEIGHT, mMatrix);
-        PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT);
-        assertEquals(0, newPoints.x, 0);
-        assertEquals(0, newPoints.y, 0);
-    }
-
-    @Test
-    public void rotate_270_bottomRight() {
-        RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_270,
-                POINT_WIDTH, POINT_HEIGHT, mMatrix);
-        PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT);
-        assertEquals(POINT_HEIGHT, newPoints.x, 0);
-        assertEquals(0, newPoints.y, 0);
-    }
-
-    private PointF checkMappedPoints(int x, int y) {
-        final float[] fs = new float[] {x, y};
-        mMatrix.mapPoints(fs);
-        return new PointF(fs[0], fs[1]);
-    }
-
-    private Bitmap createBitmap(float luma) {
-        Bitmap bitmap = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, ARGB_8888);
-        for (int i = 0; i < BITMAP_WIDTH; i++) {
-            for (int j = 0; j < BITMAP_HEIGHT; j++) {
-                bitmap.setPixel(i, j, Color.argb(1, luma, luma, luma));
-            }
-        }
-        return bitmap;
-    }
-
-    private GraphicBuffer swBitmapToGraphicsBuffer(Bitmap swBitmap) {
-        Bitmap hwBitmap = swBitmap.copy(Bitmap.Config.HARDWARE, false);
-        return hwBitmap.createGraphicBufferHandle();
-    }
-
-    private void setBorderLuma(Bitmap swBitmap, float luma) {
-        int i;
-        int width = swBitmap.getWidth();
-        int height = swBitmap.getHeight();
-        for (i = 0; i < width; i++) {
-            swBitmap.setPixel(i, 0, Color.argb(1, luma, luma, luma));
-            swBitmap.setPixel(i, height - 1, Color.argb(1, luma, luma, luma));
-        }
-        for (i = 0; i < height; i++) {
-            swBitmap.setPixel(0, i, Color.argb(1, luma, luma, luma));
-            swBitmap.setPixel(width - 1, i, Color.argb(1, luma, luma, luma));
-        }
-    }
-}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 198b4c3..bde2cfd 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -611,65 +611,91 @@
 
     int setParameter(UUID modelId, @ModelParams int modelParam, int value) {
         synchronized (mLock) {
-            MetricsLogger.count(mContext, "sth_set_parameter", 1);
-            if (modelId == null || mModule == null) {
-                return SoundTrigger.STATUS_ERROR;
-            }
-            ModelData modelData = mModelDataMap.get(modelId);
-            if (modelData == null) {
-                Slog.w(TAG, "SetParameter: Invalid model id:" + modelId);
-                return SoundTrigger.STATUS_BAD_VALUE;
-            }
-            if (!modelData.isModelLoaded()) {
-                Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelId);
-                return SoundTrigger.STATUS_BAD_VALUE;
-            }
-
-            return mModule.setParameter(modelData.getHandle(), modelParam, value);
+            return setParameterLocked(mModelDataMap.get(modelId), modelParam, value);
         }
     }
 
-    int getParameter(@NonNull UUID modelId, @ModelParams int modelParam)
-            throws UnsupportedOperationException, IllegalArgumentException {
+    int setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value) {
         synchronized (mLock) {
-            MetricsLogger.count(mContext, "sth_get_parameter", 1);
-            if (mModule == null) {
-                throw new UnsupportedOperationException("SoundTriggerModule not initialized");
-            }
-
-            ModelData modelData = mModelDataMap.get(modelId);
-            if (modelData == null) {
-                throw new IllegalArgumentException("Invalid model id:" + modelId);
-            }
-            if (!modelData.isModelLoaded()) {
-                throw new UnsupportedOperationException("Given model is not loaded:" + modelId);
-            }
-
-            return mModule.getParameter(modelData.getHandle(), modelParam);
+            return setParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam, value);
         }
     }
 
+    private int setParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam,
+            int value) {
+        MetricsLogger.count(mContext, "sth_set_parameter", 1);
+        if (mModule == null) {
+            return SoundTrigger.STATUS_NO_INIT;
+        }
+        if (modelData == null || !modelData.isModelLoaded()) {
+            Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelData);
+            return SoundTrigger.STATUS_BAD_VALUE;
+        }
+
+        return mModule.setParameter(modelData.getHandle(), modelParam, value);
+    }
+
+    int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
+        synchronized (mLock) {
+            return getParameterLocked(mModelDataMap.get(modelId), modelParam);
+        }
+    }
+
+    int getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
+        synchronized (mLock) {
+            return getParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
+        }
+    }
+
+    private int getParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam) {
+        MetricsLogger.count(mContext, "sth_get_parameter", 1);
+        if (mModule == null) {
+            throw new UnsupportedOperationException("SoundTriggerModule not initialized");
+        }
+
+        if (modelData == null) {
+            throw new IllegalArgumentException("Invalid model id");
+        }
+        if (!modelData.isModelLoaded()) {
+            throw new UnsupportedOperationException("Given model is not loaded:" + modelData);
+        }
+
+        return mModule.getParameter(modelData.getHandle(), modelParam);
+    }
+
     @Nullable
     ModelParamRange queryParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
         synchronized (mLock) {
-            MetricsLogger.count(mContext, "sth_query_parameter", 1);
-            if (mModule == null) {
-                return null;
-            }
-            ModelData modelData = mModelDataMap.get(modelId);
-            if (modelData == null) {
-                Slog.w(TAG, "queryParameter: Invalid model id:" + modelId);
-                return null;
-            }
-            if (!modelData.isModelLoaded()) {
-                Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelId);
-                return null;
-            }
-
-            return mModule.queryParameter(modelData.getHandle(), modelParam);
+            return queryParameterLocked(mModelDataMap.get(modelId), modelParam);
         }
     }
 
+    @Nullable
+    ModelParamRange queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
+        synchronized (mLock) {
+            return queryParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
+        }
+    }
+
+    @Nullable
+    private ModelParamRange queryParameterLocked(@Nullable ModelData modelData,
+            @ModelParams int modelParam) {
+        MetricsLogger.count(mContext, "sth_query_parameter", 1);
+        if (mModule == null) {
+            return null;
+        }
+        if (modelData == null) {
+            Slog.w(TAG, "queryParameter: Invalid model id");
+            return null;
+        }
+        if (!modelData.isModelLoaded()) {
+            Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelData);
+            return null;
+        }
+
+        return mModule.queryParameter(modelData.getHandle(), modelParam);
+    }
+
     //---- SoundTrigger.StatusListener methods
     @Override
     public void onRecognition(RecognitionEvent event) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
index d05e044..54dffdc 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
@@ -16,17 +16,17 @@
 
 package com.android.server.soundtrigger;
 
+import android.annotation.Nullable;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
-import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
-import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
-import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
-import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
-import android.hardware.soundtrigger.SoundTriggerModule;
+
+import com.android.server.voiceinteraction.VoiceInteractionManagerService;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -71,6 +71,58 @@
     public abstract ModuleProperties getModuleProperties();
 
     /**
+     * Set a model specific {@link ModelParams} with the given value. This
+     * parameter will keep its value for the duration the model is loaded regardless of starting and
+     * stopping recognition. Once the model is unloaded, the value will be lost.
+     * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this
+     * method.
+     *
+     * @param keyphraseId The identifier of the keyphrase for which
+     *        to modify model parameters
+     * @param modelParam   {@link ModelParams}
+     * @param value        Value to set
+     * @return - {@link SoundTrigger#STATUS_OK} in case of success
+     *         - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+     *         - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+     *           if API is not supported by HAL
+     */
+    public abstract int setParameter(int keyphraseId, @ModelParams int modelParam, int value);
+
+    /**
+     * Get a model specific {@link ModelParams}. This parameter will keep its value
+     * for the duration the model is loaded regardless of starting and stopping recognition.
+     * Once the model is unloaded, the value will be lost. If the value is not set, a default
+     * value is returned. See ModelParams for parameter default values.
+     * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this
+     * method.
+     *
+     * @param keyphraseId The identifier of the keyphrase for which
+     *        to modify model parameters
+     * @param modelParam   {@link ModelParams}
+     * @return value of parameter
+     * @throws UnsupportedOperationException if hal or model do not support this API.
+     *         queryParameter should be checked first.
+     * @throws IllegalArgumentException if invalid model handle or parameter is passed.
+     *         queryParameter should be checked first.
+     */
+    public abstract int getParameter(int keyphraseId, @ModelParams int modelParam);
+
+    /**
+     * Determine if parameter control is supported for the given model handle.
+     * This method should be checked prior to calling {@link SoundTriggerInternal#setParameter}
+     * or {@link SoundTriggerInternal#getParameter}.
+     *
+     * @param keyphraseId The identifier of the keyphrase for which
+     *        to modify model parameters
+     * @param modelParam {@link ModelParams}
+     * @return supported range of parameter, null if not supported
+     */
+    @Nullable
+    public abstract ModelParamRange queryParameter(int keyphraseId,
+            @ModelParams int modelParam);
+
+    /**
      * Unloads (and stops if running) the given keyphraseId
      */
     public abstract int unloadKeyphraseModel(int keyphaseId);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 68b16f3..e37755b 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -1446,26 +1446,45 @@
         @Override
         public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
                 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
-            if (!isInitialized()) return STATUS_ERROR;
+            if (!isInitialized()) throw new UnsupportedOperationException();
             return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
                     recognitionConfig);
         }
 
         @Override
         public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
-            if (!isInitialized()) return STATUS_ERROR;
+            if (!isInitialized()) throw new UnsupportedOperationException();
             return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
         }
 
         @Override
         public ModuleProperties getModuleProperties() {
-            if (!isInitialized()) return null;
+            if (!isInitialized()) throw new UnsupportedOperationException();
             return mSoundTriggerHelper.getModuleProperties();
         }
 
         @Override
+        public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) {
+            if (!isInitialized()) throw new UnsupportedOperationException();
+            return mSoundTriggerHelper.setKeyphraseParameter(keyphraseId, modelParam, value);
+        }
+
+        @Override
+        public int getParameter(int keyphraseId, @ModelParams int modelParam) {
+            if (!isInitialized()) throw new UnsupportedOperationException();
+            return mSoundTriggerHelper.getKeyphraseParameter(keyphraseId, modelParam);
+        }
+
+        @Override
+        @Nullable
+        public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) {
+            if (!isInitialized()) throw new UnsupportedOperationException();
+            return mSoundTriggerHelper.queryKeyphraseParameter(keyphraseId, modelParam);
+        }
+
+        @Override
         public int unloadKeyphraseModel(int keyphraseId) {
-            if (!isInitialized()) return STATUS_ERROR;
+            if (!isInitialized()) throw new UnsupportedOperationException();
             return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
         }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index e9db31b..06c8074 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -41,7 +41,9 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.os.Binder;
@@ -1084,6 +1086,55 @@
             }
         }
 
+        @Override
+        public int setParameter(IVoiceInteractionService service, int keyphraseId,
+                @ModelParams int modelParam, int value) {
+            // Allow the call if this is the current voice interaction service.
+            synchronized (this) {
+                enforceIsCurrentVoiceInteractionService(service);
+            }
+
+            final long caller = Binder.clearCallingIdentity();
+            try {
+                return mSoundTriggerInternal.setParameter(keyphraseId, modelParam, value);
+            } finally {
+                Binder.restoreCallingIdentity(caller);
+            }
+        }
+
+        @Override
+        public int getParameter(IVoiceInteractionService service, int keyphraseId,
+                @ModelParams int modelParam) {
+            // Allow the call if this is the current voice interaction service.
+            synchronized (this) {
+                enforceIsCurrentVoiceInteractionService(service);
+            }
+
+            final long caller = Binder.clearCallingIdentity();
+            try {
+                return mSoundTriggerInternal.getParameter(keyphraseId, modelParam);
+            } finally {
+                Binder.restoreCallingIdentity(caller);
+            }
+        }
+
+        @Override
+        @Nullable
+        public ModelParamRange queryParameter(IVoiceInteractionService service,
+                int keyphraseId, @ModelParams int modelParam) {
+            // Allow the call if this is the current voice interaction service.
+            synchronized (this) {
+                enforceIsCurrentVoiceInteractionService(service);
+            }
+
+            final long caller = Binder.clearCallingIdentity();
+            try {
+                return mSoundTriggerInternal.queryParameter(keyphraseId, modelParam);
+            } finally {
+                Binder.restoreCallingIdentity(caller);
+            }
+        }
+
         private synchronized void unloadAllKeyphraseModels() {
             for (int i = 0; i < mLoadedKeyphraseIds.size(); i++) {
                 final long caller = Binder.clearCallingIdentity();
diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java
index aaafee2..f39981f 100644
--- a/telephony/common/android/telephony/LocationAccessPolicy.java
+++ b/telephony/common/android/telephony/LocationAccessPolicy.java
@@ -16,8 +16,6 @@
 
 package android.telephony;
 
-import com.android.telephony.Rlog;
-
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index 368f8f1..a17a19c 100644
--- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -18,14 +18,16 @@
 
 import android.annotation.Nullable;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.permission.IPermissionManager;
 import android.provider.Settings;
-import com.android.telephony.Rlog;
+import android.util.Log;
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -76,15 +78,16 @@
      */
     public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage,
             IPackageManager packageManager, IPermissionManager permissionManager,
-            TelephonyManager telephonyManager, ContentResolver contentResolver, int userId) {
+            TelephonyManager telephonyManager, int userId, Context context) {
         if (DEBUG) {
-            Rlog.d(TAG, "disableCarrierAppsUntilPrivileged");
+            Log.d(TAG, "disableCarrierAppsUntilPrivileged");
         }
         SystemConfig config = SystemConfig.getInstance();
         ArraySet<String> systemCarrierAppsDisabledUntilUsed =
                 config.getDisabledUntilUsedPreinstalledCarrierApps();
         ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
                 config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+        ContentResolver contentResolver = getContentResolverForUser(context, userId);
         disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager,
                 telephonyManager, contentResolver, userId, systemCarrierAppsDisabledUntilUsed,
                 systemCarrierAssociatedAppsDisabledUntilUsed);
@@ -102,10 +105,10 @@
      * Manager can kill it, and this can lead to crashes as the app is in an unexpected state.
      */
     public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage,
-            IPackageManager packageManager, IPermissionManager permissionManager,
-            ContentResolver contentResolver, int userId) {
+            IPackageManager packageManager, IPermissionManager permissionManager, int userId,
+            Context context) {
         if (DEBUG) {
-            Rlog.d(TAG, "disableCarrierAppsUntilPrivileged");
+            Log.d(TAG, "disableCarrierAppsUntilPrivileged");
         }
         SystemConfig config = SystemConfig.getInstance();
         ArraySet<String> systemCarrierAppsDisabledUntilUsed =
@@ -114,15 +117,23 @@
 
         ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
                 config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+        ContentResolver contentResolver = getContentResolverForUser(context, userId);
         disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager,
                 null /* telephonyManager */, contentResolver, userId,
                 systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed);
     }
 
+    private static ContentResolver getContentResolverForUser(Context context, int userId) {
+        Context userContext = context.createContextAsUser(UserHandle.getUserHandleForUid(userId),
+                0);
+        return userContext.getContentResolver();
+    }
+
     /**
      * Disable carrier apps until they are privileged
      * Must be public b/c framework unit tests can't access package-private methods.
      */
+    // Must be public b/c framework unit tests can't access package-private methods.
     @VisibleForTesting
     public static void disableCarrierAppsUntilPrivileged(String callingPackage,
             IPackageManager packageManager, IPermissionManager permissionManager,
@@ -142,9 +153,8 @@
                 systemCarrierAssociatedAppsDisabledUntilUsed);
 
         List<String> enabledCarrierPackages = new ArrayList<>();
-
-        boolean hasRunOnce = Settings.Secure.getIntForUser(
-                contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, userId) == 1;
+        boolean hasRunOnce = Settings.Secure.getInt(contentResolver,
+                Settings.Secure.CARRIER_APPS_HANDLED, 0) == 1;
 
         try {
             for (ApplicationInfo ai : candidates) {
@@ -177,7 +187,7 @@
                             || ai.enabledSetting
                             == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
                             || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) {
-                        Rlog.i(TAG, "Update state(" + packageName + "): ENABLED for user "
+                        Log.i(TAG, "Update state(" + packageName + "): ENABLED for user "
                                 + userId);
                         packageManager.setSystemAppInstallState(
                                 packageName,
@@ -200,7 +210,7 @@
                                     == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
                                     || (associatedApp.flags
                                     & ApplicationInfo.FLAG_INSTALLED) == 0) {
-                                Rlog.i(TAG, "Update associated state(" + associatedApp.packageName
+                                Log.i(TAG, "Update associated state(" + associatedApp.packageName
                                         + "): ENABLED for user " + userId);
                                 packageManager.setSystemAppInstallState(
                                         associatedApp.packageName,
@@ -225,7 +235,7 @@
                             && ai.enabledSetting
                             == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                             && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
-                        Rlog.i(TAG, "Update state(" + packageName
+                        Log.i(TAG, "Update state(" + packageName
                                 + "): DISABLED_UNTIL_USED for user " + userId);
                         packageManager.setSystemAppInstallState(
                                 packageName,
@@ -243,7 +253,7 @@
                                         == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                                         && (associatedApp.flags
                                         & ApplicationInfo.FLAG_INSTALLED) != 0) {
-                                    Rlog.i(TAG,
+                                    Log.i(TAG,
                                             "Update associated state(" + associatedApp.packageName
                                                     + "): DISABLED_UNTIL_USED for user " + userId);
                                     packageManager.setSystemAppInstallState(
@@ -259,8 +269,7 @@
 
             // Mark the execution so we do not disable apps again.
             if (!hasRunOnce) {
-                Settings.Secure.putIntForUser(
-                        contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1, userId);
+                Settings.Secure.putInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1);
             }
 
             if (!enabledCarrierPackages.isEmpty()) {
@@ -271,7 +280,7 @@
                 permissionManager.grantDefaultPermissionsToEnabledCarrierApps(packageNames, userId);
             }
         } catch (RemoteException e) {
-            Rlog.w(TAG, "Could not reach PackageManager", e);
+            Log.w(TAG, "Could not reach PackageManager", e);
         }
     }
 
@@ -393,7 +402,7 @@
                 return ai;
             }
         } catch (RemoteException e) {
-            Rlog.w(TAG, "Could not reach PackageManager", e);
+            Log.w(TAG, "Could not reach PackageManager", e);
         }
         return null;
     }
diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
index a36ff93..5c53f7e 100644
--- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
@@ -19,7 +19,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
 import android.os.Build;
-import com.android.telephony.Rlog;
+import android.util.Log;
 import android.text.TextUtils;
 import android.util.SparseIntArray;
 
@@ -498,11 +498,11 @@
         StringBuilder ret = new StringBuilder(lengthSeptets);
 
         if (languageTable < 0 || languageTable > sLanguageTables.length) {
-            Rlog.w(TAG, "unknown language table " + languageTable + ", using default");
+            Log.w(TAG, "unknown language table " + languageTable + ", using default");
             languageTable = 0;
         }
         if (shiftTable < 0 || shiftTable > sLanguageShiftTables.length) {
-            Rlog.w(TAG, "unknown single shift table " + shiftTable + ", using default");
+            Log.w(TAG, "unknown single shift table " + shiftTable + ", using default");
             shiftTable = 0;
         }
 
@@ -512,11 +512,11 @@
             String shiftTableToChar = sLanguageShiftTables[shiftTable];
 
             if (languageTableToChar.isEmpty()) {
-                Rlog.w(TAG, "no language table for code " + languageTable + ", using default");
+                Log.w(TAG, "no language table for code " + languageTable + ", using default");
                 languageTableToChar = sLanguageTables[0];
             }
             if (shiftTableToChar.isEmpty()) {
-                Rlog.w(TAG, "no single shift table for code " + shiftTable + ", using default");
+                Log.w(TAG, "no single shift table for code " + shiftTable + ", using default");
                 shiftTableToChar = sLanguageShiftTables[0];
             }
 
@@ -556,7 +556,7 @@
                 }
             }
         } catch (RuntimeException ex) {
-            Rlog.e(TAG, "Error GSM 7 bit packed: ", ex);
+            Log.e(TAG, "Error GSM 7 bit packed: ", ex);
             return null;
         }
 
@@ -813,7 +813,7 @@
         for (int i = 0; i < sz; i++) {
             char c = s.charAt(i);
             if (c == GSM_EXTENDED_ESCAPE) {
-                Rlog.w(TAG, "countGsmSeptets() string contains Escape character, skipping.");
+                Log.w(TAG, "countGsmSeptets() string contains Escape character, skipping.");
                 continue;
             }
             if (charToLanguageTable.get(c, -1) != -1) {
@@ -892,7 +892,7 @@
         for (int i = 0; i < sz && !lpcList.isEmpty(); i++) {
             char c = s.charAt(i);
             if (c == GSM_EXTENDED_ESCAPE) {
-                Rlog.w(TAG, "countGsmSeptets() string contains Escape character, ignoring!");
+                Log.w(TAG, "countGsmSeptets() string contains Escape character, ignoring!");
                 continue;
             }
             // iterate through enabled locking shift tables
@@ -1496,7 +1496,7 @@
         int numTables = sLanguageTables.length;
         int numShiftTables = sLanguageShiftTables.length;
         if (numTables != numShiftTables) {
-            Rlog.e(TAG, "Error: language tables array length " + numTables +
+            Log.e(TAG, "Error: language tables array length " + numTables +
                     " != shift tables array length " + numShiftTables);
         }
 
@@ -1506,7 +1506,7 @@
 
             int tableLen = table.length();
             if (tableLen != 0 && tableLen != 128) {
-                Rlog.e(TAG, "Error: language tables index " + i +
+                Log.e(TAG, "Error: language tables index " + i +
                         " length " + tableLen + " (expected 128 or 0)");
             }
 
@@ -1524,7 +1524,7 @@
 
             int shiftTableLen = shiftTable.length();
             if (shiftTableLen != 0 && shiftTableLen != 128) {
-                Rlog.e(TAG, "Error: language shift tables index " + i +
+                Log.e(TAG, "Error: language shift tables index " + i +
                         " length " + shiftTableLen + " (expected 128 or 0)");
             }
 
diff --git a/telephony/common/com/android/internal/telephony/HbpcdUtils.java b/telephony/common/com/android/internal/telephony/HbpcdUtils.java
index 45a563c..714f5a6 100644
--- a/telephony/common/com/android/internal/telephony/HbpcdUtils.java
+++ b/telephony/common/com/android/internal/telephony/HbpcdUtils.java
@@ -19,7 +19,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
-import com.android.telephony.Rlog;
+import android.util.Log;
 
 import com.android.internal.telephony.HbpcdLookup.ArbitraryMccSidMatch;
 import com.android.internal.telephony.HbpcdLookup.MccIdd;
@@ -54,16 +54,16 @@
         if (c2 != null) {
             int c2Counter = c2.getCount();
             if (DBG) {
-                Rlog.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter);
+                Log.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter);
             }
             if (c2Counter == 1) {
                 if (DBG) {
-                    Rlog.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2);
+                    Log.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2);
                 }
                 c2.moveToFirst();
                 tmpMcc = c2.getInt(0);
                 if (DBG) {
-                    Rlog.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc);
+                    Log.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc);
                 }
                 c2.close();
                 return tmpMcc;
@@ -85,18 +85,18 @@
             int c3Counter = c3.getCount();
             if (c3Counter > 0) {
                 if (c3Counter > 1) {
-                    Rlog.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3);
+                    Log.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3);
                 }
-                if (DBG) Rlog.d(LOG_TAG, "Query conflict sid returned the cursor " + c3);
+                if (DBG) Log.d(LOG_TAG, "Query conflict sid returned the cursor " + c3);
                 c3.moveToFirst();
                 tmpMcc = c3.getInt(0);
                 if (DBG) {
-                    Rlog.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc);
+                    Log.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc);
                 }
                 if (!isNitzTimeZone) {
                     // time zone is not accurate, it may get wrong mcc, ignore it.
                     if (DBG) {
-                        Rlog.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc);
+                        Log.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc);
                     }
                     tmpMcc = 0;
                 }
@@ -115,18 +115,18 @@
                 null, null);
         if (c5 != null) {
             if (c5.getCount() > 0) {
-                if (DBG) Rlog.d(LOG_TAG, "Query Range returned the cursor " + c5);
+                if (DBG) Log.d(LOG_TAG, "Query Range returned the cursor " + c5);
                 c5.moveToFirst();
                 tmpMcc = c5.getInt(0);
-                if (DBG) Rlog.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc);
+                if (DBG) Log.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc);
                 c5.close();
                 return tmpMcc;
             }
             c5.close();
         }
-        if (DBG) Rlog.d(LOG_TAG, "SID NOT found in mcc_sid_range.");
+        if (DBG) Log.d(LOG_TAG, "SID NOT found in mcc_sid_range.");
 
-        if (DBG) Rlog.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc =  " + tmpMcc);
+        if (DBG) Log.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc =  " + tmpMcc);
         // If unknown MCC still could not be resolved,
         return tmpMcc;
     }
@@ -135,7 +135,7 @@
      *  Gets country information with given MCC.
     */
     public String getIddByMcc(int mcc) {
-        if (DBG) Rlog.d(LOG_TAG, "Enter getHbpcdInfoByMCC.");
+        if (DBG) Log.d(LOG_TAG, "Enter getHbpcdInfoByMCC.");
         String idd = "";
 
         Cursor c = null;
@@ -145,19 +145,19 @@
                 MccIdd.MCC + "=" + mcc, null, null);
         if (cur != null) {
             if (cur.getCount() > 0) {
-                if (DBG) Rlog.d(LOG_TAG, "Query Idd returned the cursor " + cur);
+                if (DBG) Log.d(LOG_TAG, "Query Idd returned the cursor " + cur);
                 // TODO: for those country having more than 1 IDDs, need more information
                 // to decide which IDD would be used. currently just use the first 1.
                 cur.moveToFirst();
                 idd = cur.getString(0);
-                if (DBG) Rlog.d(LOG_TAG, "IDD = " + idd);
+                if (DBG) Log.d(LOG_TAG, "IDD = " + idd);
 
             }
             cur.close();
         }
         if (c != null) c.close();
 
-        if (DBG) Rlog.d(LOG_TAG, "Exit getHbpcdInfoByMCC.");
+        if (DBG) Log.d(LOG_TAG, "Exit getHbpcdInfoByMCC.");
         return idd;
     }
 }
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index afb9b6f..9b82828 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -40,7 +40,7 @@
 import android.provider.Telephony;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.PackageChangeReceiver;
-import com.android.telephony.Rlog;
+import android.util.Log;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
@@ -565,7 +565,7 @@
             int mode = appOps.unsafeCheckOp(opStr, applicationData.mUid,
                     applicationData.mPackageName);
             if (mode != AppOpsManager.MODE_ALLOWED) {
-                Rlog.e(LOG_TAG, applicationData.mPackageName + " lost "
+                Log.e(LOG_TAG, applicationData.mPackageName + " lost "
                         + opStr + ": "
                         + (updateIfNeeded ? " (fixing)" : " (no permission to fix)"));
                 if (updateIfNeeded) {
@@ -643,7 +643,7 @@
                     int uid = packageManager.getPackageInfo(oldPackageName, 0).applicationInfo.uid;
                     setExclusiveAppops(oldPackageName, appOps, uid, AppOpsManager.MODE_DEFAULT);
                 } catch (NameNotFoundException e) {
-                    Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName);
+                    Log.w(LOG_TAG, "Old SMS package not found: " + oldPackageName);
                 }
             }
 
@@ -750,7 +750,7 @@
         // the package signature matches system signature.
         final int result = packageManager.checkSignatures(context.getPackageName(), packageName);
         if (result != PackageManager.SIGNATURE_MATCH) {
-            Rlog.e(LOG_TAG, packageName + " does not have system signature");
+            Log.e(LOG_TAG, packageName + " does not have system signature");
             return;
         }
         try {
@@ -758,13 +758,13 @@
             int mode = appOps.unsafeCheckOp(AppOpsManager.OPSTR_WRITE_SMS, info.applicationInfo.uid,
                     packageName);
             if (mode != AppOpsManager.MODE_ALLOWED) {
-                Rlog.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS:  (fixing)");
+                Log.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS:  (fixing)");
                 setExclusiveAppops(packageName, appOps, info.applicationInfo.uid,
                         AppOpsManager.MODE_ALLOWED);
             }
         } catch (NameNotFoundException e) {
             // No whitelisted system app on this device
-            Rlog.e(LOG_TAG, "Package not found: " + packageName);
+            Log.e(LOG_TAG, "Package not found: " + packageName);
         }
 
     }
diff --git a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
index 06c3728..cd365a1 100644
--- a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
+++ b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
@@ -24,13 +24,16 @@
 import android.os.SystemProperties;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
-import com.android.telephony.Rlog;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
 
 import com.android.internal.telephony.HbpcdLookup.MccIdd;
 import com.android.internal.telephony.HbpcdLookup.MccLookup;
 
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.HashMap;
 
@@ -143,7 +146,7 @@
 
         // First check whether the number is a NANP number.
         int nanpState = checkNANP(numberEntry, allIDDs);
-        if (DBG) Rlog.d(TAG, "NANP type: " + getNumberPlanType(nanpState));
+        if (DBG) Log.d(TAG, "NANP type: " + getNumberPlanType(nanpState));
 
         if ((nanpState == NP_NANP_LOCAL)
             || (nanpState == NP_NANP_AREA_LOCAL)
@@ -173,7 +176,7 @@
 
         int internationalState = checkInternationalNumberPlan(context, numberEntry, allIDDs,
                 NANP_IDD);
-        if (DBG) Rlog.d(TAG, "International type: " + getNumberPlanType(internationalState));
+        if (DBG) Log.d(TAG, "International type: " + getNumberPlanType(internationalState));
         String returnNumber = null;
 
         switch (internationalState) {
@@ -272,7 +275,7 @@
                 }
             }
         } catch (SQLException e) {
-            Rlog.e(TAG, "Can't access HbpcdLookup database", e);
+            Log.e(TAG, "Can't access HbpcdLookup database", e);
         } finally {
             if (cursor != null) {
                 cursor.close();
@@ -281,7 +284,7 @@
 
         IDDS_MAPS.put(mcc, allIDDs);
 
-        if (DBG) Rlog.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs);
+        if (DBG) Log.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs);
         return allIDDs;
     }
 
@@ -472,7 +475,7 @@
                 int tempCC = allCCs[i];
                 for (int j = 0; j < MAX_COUNTRY_CODES_LENGTH; j ++) {
                     if (tempCC == ccArray[j]) {
-                        if (DBG) Rlog.d(TAG, "Country code = " + tempCC);
+                        if (DBG) Log.d(TAG, "Country code = " + tempCC);
                         return tempCC;
                     }
                 }
@@ -509,7 +512,7 @@
                 }
             }
         } catch (SQLException e) {
-            Rlog.e(TAG, "Can't access HbpcdLookup database", e);
+            Log.e(TAG, "Can't access HbpcdLookup database", e);
         } finally {
             if (cursor != null) {
                 cursor.close();
@@ -561,10 +564,10 @@
      * Filter the destination number if using VZW sim card.
      */
     public static String filterDestAddr(Context context, int subId, String destAddr) {
-        if (DBG) Rlog.d(TAG, "enter filterDestAddr. destAddr=\"" + Rlog.pii(TAG, destAddr) + "\"" );
+        if (DBG) Log.d(TAG, "enter filterDestAddr. destAddr=\"" + pii(TAG, destAddr) + "\"" );
 
         if (destAddr == null || !PhoneNumberUtils.isGlobalPhoneNumber(destAddr)) {
-            Rlog.w(TAG, "destAddr" + Rlog.pii(TAG, destAddr) +
+            Log.w(TAG, "destAddr" + pii(TAG, destAddr) +
                     " is not a global phone number! Nothing changed.");
             return destAddr;
         }
@@ -585,9 +588,9 @@
         }
 
         if (DBG) {
-            Rlog.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted."));
-            Rlog.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? Rlog.pii(TAG,
-                    result) : Rlog.pii(TAG, destAddr)) + "\"");
+            Log.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted."));
+            Log.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? pii(TAG,
+                    result) : pii(TAG, destAddr)) + "\"");
         }
         return result != null ? result : destAddr;
     }
@@ -608,7 +611,7 @@
                 networkType = CDMA_HOME_NETWORK;
             }
         } else {
-            if (DBG) Rlog.w(TAG, "warning! unknown mPhoneType value=" + phoneType);
+            if (DBG) Log.w(TAG, "warning! unknown mPhoneType value=" + phoneType);
         }
 
         return networkType;
@@ -650,4 +653,44 @@
         // by default this value is false
         return false;
     }
+
+    /**
+     * Redact personally identifiable information for production users.
+     * @param tag used to identify the source of a log message
+     * @param pii the personally identifiable information we want to apply secure hash on.
+     * @return If tag is loggable in verbose mode or pii is null, return the original input.
+     * otherwise return a secure Hash of input pii
+     */
+    private static String pii(String tag, Object pii) {
+        String val = String.valueOf(pii);
+        if (pii == null || TextUtils.isEmpty(val) || Log.isLoggable(tag, Log.VERBOSE)) {
+            return val;
+        }
+        return "[" + secureHash(val.getBytes()) + "]";
+    }
+
+    /**
+     * Returns a secure hash (using the SHA1 algorithm) of the provided input.
+     *
+     * @return "****" if the build type is user, otherwise the hash
+     * @param input the bytes for which the secure hash should be computed.
+     */
+    private static String secureHash(byte[] input) {
+        // Refrain from logging user personal information in user build.
+        if (android.os.Build.IS_USER) {
+            return "****";
+        }
+
+        MessageDigest messageDigest;
+
+        try {
+            messageDigest = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException e) {
+            return "####";
+        }
+
+        byte[] result = messageDigest.digest(input);
+        return Base64.encodeToString(
+                result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
+    }
 }
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 4109ca6..f6ce0dc 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -28,7 +28,6 @@
 import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
-import com.android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
@@ -521,7 +520,7 @@
             return;
         }
 
-        if (DBG) Rlog.d(LOG_TAG, "No modify permission, check carrier privilege next.");
+        if (DBG) Log.d(LOG_TAG, "No modify permission, check carrier privilege next.");
         enforceCallingOrSelfCarrierPrivilege(context, subId, message);
     }
 
@@ -539,7 +538,7 @@
         }
 
         if (DBG) {
-            Rlog.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next.");
+            Log.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next.");
         }
 
         enforceCallingOrSelfCarrierPrivilege(context, subId, message);
@@ -559,7 +558,7 @@
         }
 
         if (DBG) {
-            Rlog.d(LOG_TAG, "No READ_PRIVILEDED_PHONE_STATE permission, "
+            Log.d(LOG_TAG, "No READ_PRIVILEDED_PHONE_STATE permission, "
                     + "check carrier privilege next.");
         }
 
@@ -584,7 +583,7 @@
             Context context, int subId, int uid, String message) {
         if (getCarrierPrivilegeStatus(context, subId, uid)
                 != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
-            if (DBG) Rlog.e(LOG_TAG, "No Carrier Privilege.");
+            if (DBG) Log.e(LOG_TAG, "No Carrier Privilege.");
             throw new SecurityException(message);
         }
     }
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index 8e703fe..3f0aeb5 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -16,16 +16,17 @@
 
 package android.telephony;
 
-import com.android.telephony.Rlog;
-
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.hardware.radio.V1_0.CellInfoType;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.telephony.Rlog;
+
 import java.util.Objects;
 import java.util.UUID;
 
@@ -324,6 +325,86 @@
     }
 
     /** @hide */
+    public static CellIdentity create(android.hardware.radio.V1_0.CellIdentity cellIdentity) {
+        if (cellIdentity == null)  return null;
+        switch(cellIdentity.cellInfoType) {
+            case CellInfoType.GSM: {
+                if (cellIdentity.cellIdentityGsm.size() == 1) {
+                    return new CellIdentityGsm(cellIdentity.cellIdentityGsm.get(0));
+                }
+                break;
+            }
+            case CellInfoType.WCDMA: {
+                if (cellIdentity.cellIdentityWcdma.size() == 1) {
+                    return new CellIdentityWcdma(cellIdentity.cellIdentityWcdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.TD_SCDMA: {
+                if (cellIdentity.cellIdentityTdscdma.size() == 1) {
+                    return new  CellIdentityTdscdma(cellIdentity.cellIdentityTdscdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.LTE: {
+                if (cellIdentity.cellIdentityLte.size() == 1) {
+                    return new CellIdentityLte(cellIdentity.cellIdentityLte.get(0));
+                }
+                break;
+            }
+            case CellInfoType.CDMA: {
+                if (cellIdentity.cellIdentityCdma.size() == 1) {
+                    return new CellIdentityCdma(cellIdentity.cellIdentityCdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.NONE: break;
+            default: break;
+        }
+        return null;
+    }
+
+    /** @hide */
+    public static CellIdentity create(android.hardware.radio.V1_2.CellIdentity cellIdentity) {
+        if (cellIdentity == null)  return null;
+        switch(cellIdentity.cellInfoType) {
+            case CellInfoType.GSM: {
+                if (cellIdentity.cellIdentityGsm.size() == 1) {
+                    return new CellIdentityGsm(cellIdentity.cellIdentityGsm.get(0));
+                }
+                break;
+            }
+            case CellInfoType.WCDMA: {
+                if (cellIdentity.cellIdentityWcdma.size() == 1) {
+                    return new CellIdentityWcdma(cellIdentity.cellIdentityWcdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.TD_SCDMA: {
+                if (cellIdentity.cellIdentityTdscdma.size() == 1) {
+                    return new  CellIdentityTdscdma(cellIdentity.cellIdentityTdscdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.LTE: {
+                if (cellIdentity.cellIdentityLte.size() == 1) {
+                    return new CellIdentityLte(cellIdentity.cellIdentityLte.get(0));
+                }
+                break;
+            }
+            case CellInfoType.CDMA: {
+                if (cellIdentity.cellIdentityCdma.size() == 1) {
+                    return new CellIdentityCdma(cellIdentity.cellIdentityCdma.get(0));
+                }
+                break;
+            }
+            case CellInfoType.NONE: break;
+            default: break;
+        }
+        return null;
+    }
+
+    /** @hide */
     public static CellIdentity create(android.hardware.radio.V1_5.CellIdentity ci) {
         if (ci == null) return null;
         switch (ci.getDiscriminator()) {
diff --git a/telephony/java/android/telephony/CellLocation.java b/telephony/java/android/telephony/CellLocation.java
index 1193199..2d0bd52 100644
--- a/telephony/java/android/telephony/CellLocation.java
+++ b/telephony/java/android/telephony/CellLocation.java
@@ -53,8 +53,7 @@
     /**
      * Create a new CellLocation from a intent notifier Bundle
      *
-     * This method is used by PhoneStateIntentReceiver and maybe by
-     * external applications.
+     * This method maybe used by external applications.
      *
      * @param bundle Bundle from intent notifier
      * @return newly created CellLocation
diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java
index 39af34c..c706d28 100644
--- a/telephony/java/android/telephony/ImsManager.java
+++ b/telephony/java/android/telephony/ImsManager.java
@@ -17,6 +17,8 @@
 package android.telephony.ims;
 
 import android.annotation.NonNull;
+import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -35,7 +37,26 @@
 
     private Context mContext;
 
-    /** @hide */
+    /**
+     * <p>Broadcast Action: Indicates that an IMS operation was rejected by the network due to it
+     * not being authorized on the network.
+     * May include the {@link SubscriptionManager#EXTRA_SUBSCRIPTION_INDEX} extra to also specify
+     * which subscription the operation was rejected for.
+     * <p class="note">
+     * Carrier applications may listen to this broadcast to be notified of possible IMS provisioning
+     * issues.
+     */
+    // Moved from TelephonyIntents, need to keep backwards compatibility with OEM apps that have
+    // this value hard-coded in BroadcastReceiver.
+    @SuppressLint("ActionValue")
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION =
+            "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
+
+    /**
+     * Use {@link Context#getSystemService(String)} to get an instance of this class.
+     * @hide
+     */
     public ImsManager(@NonNull Context context) {
         mContext = context;
     }
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 1f7d55f..1c58f8f 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -87,8 +87,7 @@
     /**
      * Create a new SignalStrength from a intent notifier Bundle
      *
-     * This method is used by PhoneStateIntentReceiver and maybe by
-     * external applications.
+     * This method may be used by external applications.
      *
      * @param m Bundle from intent notifier
      * @return newly created SignalStrength
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index db33be3..eb328a7 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2448,18 +2448,14 @@
      * @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 m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
-        if (m != null) {
-            m.sendMultimediaMessage(getSubscriptionId(), contentUri, locationUrl, configOverrides,
-                    sentIntent);
-        }
+        MmsManager.getInstance().sendMultimediaMessage(getSubscriptionId(), contentUri,
+                    locationUrl, configOverrides, sentIntent);
     }
 
     /**
@@ -2483,7 +2479,6 @@
      * @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) {
@@ -2493,11 +2488,8 @@
         if (contentUri == null) {
             throw new IllegalArgumentException("Uri contentUri null");
         }
-        MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
-        if (m != null) {
-            m.downloadMultimediaMessage(getSubscriptionId(), locationUrl, contentUri,
-                    configOverrides, downloadedIntent);
-        }
+        MmsManager.getInstance().downloadMultimediaMessage(getSubscriptionId(), locationUrl,
+                contentUri, configOverrides, downloadedIntent);
     }
 
     // MMS send/download failure result codes
@@ -2539,9 +2531,9 @@
      * </p>
      *
      * @return the bundle key/values pairs that contains MMS configuration values
-     *  or an empty bundle if they cannot be found.
      */
-    @NonNull public Bundle getCarrierConfigValues() {
+    @Nullable
+    public Bundle getCarrierConfigValues() {
         try {
             ISms iSms = getISmsService();
             if (iSms != null) {
@@ -2550,7 +2542,7 @@
         } catch (RemoteException ex) {
             // ignore it
         }
-        return new Bundle();
+        return null;
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 843c065..157d92d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -46,6 +46,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.net.ConnectivityManager;
 import android.net.NetworkStats;
@@ -449,12 +450,8 @@
             case UNKNOWN:
                 modemCount = MODEM_COUNT_SINGLE_MODEM;
                 // check for voice and data support, 0 if not supported
-                if (!isVoiceCapable() && !isSmsCapable() && mContext != null) {
-                    ConnectivityManager cm = (ConnectivityManager) mContext
-                            .getSystemService(Context.CONNECTIVITY_SERVICE);
-                    if (cm != null && !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) {
-                        modemCount = MODEM_COUNT_NO_MODEM;
-                    }
+                if (!isVoiceCapable() && !isSmsCapable() && !isDataCapable()) {
+                    modemCount = MODEM_COUNT_NO_MODEM;
                 }
                 break;
             case DSDS:
@@ -1447,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>
@@ -5061,7 +5040,9 @@
      *      not present or not loaded
      * @hide
      */
-    @UnsupportedAppUsage
+    @Nullable
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public String[] getIsimImpu() {
         try {
             IPhoneSubInfo info = getSubscriberInfo();
@@ -10669,12 +10650,21 @@
     }
 
     /**
+     * Checks whether cellular data connection is enabled in the device.
+     *
+     * Whether cellular data connection is enabled, meaning upon request whether will try to setup
+     * metered data connection considering all factors below:
+     * 1) User turned on data setting {@link #isDataEnabled}.
+     * 2) Carrier allows data to be on.
+     * 3) Network policy.
+     * And possibly others.
+     *
+     * @return {@code true} if the overall data connection is capable; {@code false} if not.
      * @hide
-     * It's similar to isDataEnabled, but unlike isDataEnabled, this API also evaluates
-     * carrierDataEnabled, policyDataEnabled etc to give a final decision of whether mobile data is
-     * capable of using.
      */
-    public boolean isDataCapable() {
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public boolean isDataConnectionEnabled() {
         boolean retVal = false;
         try {
             int subId = getSubId(SubscriptionManager.getDefaultDataSubscriptionId());
@@ -10682,13 +10672,24 @@
             if (telephony != null)
                 retVal = telephony.isDataEnabled(subId);
         } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#isDataEnabled", e);
+            Log.e(TAG, "Error isDataConnectionEnabled", e);
         } catch (NullPointerException e) {
         }
         return retVal;
     }
 
     /**
+     * Checks if FEATURE_TELEPHONY_DATA is enabled.
+     *
+     * @hide
+     */
+    public boolean isDataCapable() {
+        if (mContext == null) return true;
+        return mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_DATA);
+    }
+
+    /**
      * In this mode, modem will not send specified indications when screen is off.
      * @hide
      */
diff --git a/telephony/java/android/telephony/VoLteServiceState.java b/telephony/java/android/telephony/VoLteServiceState.java
index 1214012..d4a27d9 100644
--- a/telephony/java/android/telephony/VoLteServiceState.java
+++ b/telephony/java/android/telephony/VoLteServiceState.java
@@ -53,8 +53,7 @@
     /**
      * Create a new VoLteServiceState from a intent notifier Bundle
      *
-     * This method is used by PhoneStateIntentReceiver and maybe by
-     * external applications.
+     * This method is maybe used by external applications.
      *
      * @param m Bundle from intent notifier
      * @return newly created VoLteServiceState
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index c962714..d483291 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -27,6 +27,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.telephony.AccessNetworkConstants;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.IImsRcsController;
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index f052180..7ffba7a 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -84,6 +84,11 @@
             "STRING_QUERY_RESULT_ERROR_NOT_READY";
 
     /**
+     * There is no existing configuration for the queried provisioning key.
+     */
+    public static final int PROVISIONING_RESULT_UNKNOWN = -1;
+
+    /**
      * The integer result of provisioning for the queried key is disabled.
      */
     public static final int PROVISIONING_VALUE_DISABLED = 0;
@@ -94,6 +99,151 @@
     public static final int PROVISIONING_VALUE_ENABLED = 1;
 
 
+    // Inheriting values from ImsConfig for backwards compatibility.
+    /**
+     * An integer key representing the SIP T1 timer value in milliseconds for the associated
+     * subscription.
+     * <p>
+     * The SIP T1 timer is an estimate of the round-trip time and will retransmit
+     * INVITE transactions that are longer than T1 milliseconds over unreliable transports, doubling
+     * the time before retransmission every time there is no response. See RFC3261, section 17.1.1.1
+     * for more details.
+     * <p>
+     * The value is an integer.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_T1_TIMER_VALUE_MS = 7;
+
+    /**
+     * An integer key representing the voice over LTE (VoLTE) provisioning status for the
+     * associated subscription. Determines whether the user can register for voice services over
+     * LTE.
+     * <p>
+     * Use {@link #PROVISIONING_VALUE_ENABLED} to enable VoLTE provisioning and
+     * {@link #PROVISIONING_VALUE_DISABLED} to disable VoLTE provisioning.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_VOLTE_PROVISIONING_STATUS = 10;
+
+    /**
+     * An integer key representing the video telephony (VT) provisioning status for the
+     * associated subscription. Determines whether the user can register for video services over
+     * LTE.
+     * <p>
+     * Use {@link #PROVISIONING_VALUE_ENABLED} to enable VT provisioning and
+     * {@link #PROVISIONING_VALUE_DISABLED} to disable VT provisioning.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_VT_PROVISIONING_STATUS = 11;
+
+    /**
+     * An integer key associated with the carrier configured SIP PUBLISH timer, which dictates the
+     * expiration time in seconds for published online availability in RCS presence.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15;
+
+    /**
+     * An integer key associated with the carrier configured expiration time in seconds for
+     * RCS presence published offline availability in RCS presence.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16;
+
+    /**
+     * An integer key associated with whether or not capability discovery is provisioned for this
+     * subscription. Any capability requests will be ignored by the RCS service.
+     * <p>
+     * The value is an integer, either {@link #PROVISIONING_VALUE_DISABLED} if capability
+     * discovery is disabled or {@link #PROVISIONING_VALUE_ENABLED} if capability discovery is
+     * enabled.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17;
+
+    /**
+     * An integer key associated with the period of time the capability information of each contact
+     * is cached on the device.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18;
+
+    /**
+     * An integer key associated with the period of time in seconds that the availability
+     * information of a contact is cached on the device.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19;
+
+    /**
+     * An integer key associated with the carrier configured interval in seconds expected between
+     * successive capability polling attempts.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20;
+
+    /**
+     * An integer key representing the minimum time allowed between two consecutive presence publish
+     * messages from the device.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21;
+
+    /**
+     * An integer key associated with the maximum number of MDNs contained in one SIP Request
+     * Contained List (RCS) used to retrieve the RCS capabilities of the contacts book.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22;
+
+    /**
+     * An integer associated with the expiration timer used duriing the SIP subscription of a
+     * Request Contained List (RCL), which is used to retrieve the RCS capabilities of the contact
+     * book.
+     * <p>
+     * Value is in Integer format.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23;
+
+    /**
+     * An integer key representing the RCS enhanced address book (EAB) provisioning status for the
+     * associated subscription. Determines whether or not SIP OPTIONS or presence will be used to
+     * retrieve RCS capabilities for the user's contacts.
+     * <p>
+     * Use {@link #PROVISIONING_VALUE_ENABLED} to enable EAB provisioning and
+     * {@link #PROVISIONING_VALUE_DISABLED} to disable EAB provisioning.
+     * @see #setProvisioningIntValue(int, int)
+     * @see #getProvisioningIntValue(int)
+     */
+    public static final int KEY_EAB_PROVISIONING_STATUS = 25;
+
     /**
      * Override the user-defined WiFi Roaming enabled setting for this subscription, defined in
      * {@link SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, for the purposes of provisioning
@@ -269,7 +419,7 @@
      *
      * @param key An integer that represents the provisioning key, which is defined by the OEM.
      * @return an integer value for the provided key, or
-     * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist.
+     * {@link #PROVISIONING_RESULT_UNKNOWN} if the key doesn't exist.
      * @throws IllegalArgumentException if the key provided was invalid.
      */
     @WorkerThread
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index 492170b..893a311 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -35,6 +36,7 @@
  * Contains the User Capability Exchange capabilities corresponding to a contact's URI.
  * @hide
  */
+@SystemApi
 public final class RcsContactUceCapability implements Parcelable {
 
     /** Supports 1-to-1 chat */
@@ -135,7 +137,7 @@
          * @param type The capability to map to a service URI that is different from the contact's
          *         URI.
          */
-        public Builder add(@CapabilityFlag int type, @NonNull Uri serviceUri) {
+        public @NonNull Builder add(@CapabilityFlag int type, @NonNull Uri serviceUri) {
             mCapabilities.mCapabilities |= type;
             // Put each of these capabilities into the map separately.
             for (int shift = 0; shift < Integer.SIZE; shift++) {
@@ -157,7 +159,7 @@
          * Add a UCE capability flag that this contact supports.
          * @param type the capability that the contact supports.
          */
-        public Builder add(@CapabilityFlag int type) {
+        public @NonNull Builder add(@CapabilityFlag int type) {
             mCapabilities.mCapabilities |= type;
             return this;
         }
@@ -167,7 +169,7 @@
          * @param extension A string containing a carrier specific service tag that is an extension
          *         of the {@link CapabilityFlag}s that are defined here.
          */
-        public Builder add(@NonNull String extension) {
+        public @NonNull Builder add(@NonNull String extension) {
             mCapabilities.mExtensionTags.add(extension);
             return this;
         }
@@ -175,7 +177,7 @@
         /**
          * @return the constructed instance.
          */
-        public RcsContactUceCapability build() {
+        public @NonNull RcsContactUceCapability build() {
             return mCapabilities;
         }
     }
@@ -205,7 +207,7 @@
         }
     }
 
-    public static final Creator<RcsContactUceCapability> CREATOR =
+    public static final @NonNull Creator<RcsContactUceCapability> CREATOR =
             new Creator<RcsContactUceCapability>() {
         @Override
         public RcsContactUceCapability createFromParcel(Parcel in) {
@@ -219,7 +221,7 @@
     };
 
     @Override
-    public void writeToParcel(Parcel out, int flags) {
+    public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeParcelable(mContactUri, 0);
         out.writeInt(mCapabilities);
         out.writeStringList(mExtensionTags);
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index d18e93c..60cf216 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
+import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.aidl.IImsConfig;
 import android.telephony.ims.aidl.IImsConfigCallback;
 import android.util.Log;
@@ -228,7 +229,8 @@
      * The configuration requested resulted in an unknown result. This may happen if the
      * IMS configurations are unavailable.
      */
-    public static final int CONFIG_RESULT_UNKNOWN = -1;
+    public static final int CONFIG_RESULT_UNKNOWN = ProvisioningManager.PROVISIONING_RESULT_UNKNOWN;
+
     /**
      * Setting the configuration value completed.
      */
diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java
index 9116a3b..0d86e2b 100644
--- a/telephony/java/com/android/ims/ImsConfig.java
+++ b/telephony/java/com/android/ims/ImsConfig.java
@@ -178,7 +178,7 @@
          * SIP T1 timer value in milliseconds. See RFC 3261 for define.
          * Value is in Integer format.
          */
-        public static final int SIP_T1_TIMER = 7;
+        public static final int SIP_T1_TIMER = ProvisioningManager.KEY_T1_TIMER_VALUE_MS;
 
         /**
          * SIP T2 timer value in milliseconds.  See RFC 3261 for define.
@@ -196,13 +196,15 @@
          * VoLTE status for VLT/s status of Enabled (1), or Disabled (0).
          * Value is in Integer format.
          */
-        public static final int VLT_SETTING_ENABLED = 10;
+        public static final int VLT_SETTING_ENABLED =
+                ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS;
 
         /**
          * VoLTE status for LVC/s status of Enabled (1), or Disabled (0).
          * Value is in Integer format.
          */
-        public static final int LVC_SETTING_ENABLED = 11;
+        public static final int LVC_SETTING_ENABLED =
+                ProvisioningManager.KEY_VT_PROVISIONING_STATUS;
         /**
          * Domain Name for the device to populate the request URI for REGISTRATION.
          * Value is in String format.
@@ -222,48 +224,56 @@
          * Requested expiration for Published Online availability.
          * Value is in Integer format.
          */
-        public static final int PUBLISH_TIMER = 15;
+        public static final int PUBLISH_TIMER = ProvisioningManager.KEY_RCS_PUBLISH_TIMER_SEC;
         /**
          * Requested expiration for Published Offline availability.
          * Value is in Integer format.
          */
-        public static final int PUBLISH_TIMER_EXTENDED = 16;
+        public static final int PUBLISH_TIMER_EXTENDED =
+                ProvisioningManager.KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC;
         /**
          *
          * Value is in Integer format.
          */
-        public static final int CAPABILITY_DISCOVERY_ENABLED = 17;
+        public static final int CAPABILITY_DISCOVERY_ENABLED =
+                ProvisioningManager.KEY_RCS_CAPABILITY_DISCOVERY_ENABLED;
         /**
          * Period of time the capability information of the  contact is cached on handset.
          * Value is in Integer format.
          */
-        public static final int CAPABILITIES_CACHE_EXPIRATION = 18;
+        public static final int CAPABILITIES_CACHE_EXPIRATION =
+                ProvisioningManager.KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC;
         /**
          * Peiod of time the availability information of a contact is cached on device.
          * Value is in Integer format.
          */
-        public static final int AVAILABILITY_CACHE_EXPIRATION = 19;
+        public static final int AVAILABILITY_CACHE_EXPIRATION =
+                ProvisioningManager.KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC;
         /**
          * Interval between successive capabilities polling.
          * Value is in Integer format.
          */
-        public static final int CAPABILITIES_POLL_INTERVAL = 20;
+        public static final int CAPABILITIES_POLL_INTERVAL =
+                ProvisioningManager.KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC;
         /**
          * Minimum time between two published messages from the device.
          * Value is in Integer format.
          */
-        public static final int SOURCE_THROTTLE_PUBLISH = 21;
+        public static final int SOURCE_THROTTLE_PUBLISH =
+                ProvisioningManager.KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS;
         /**
          * The Maximum number of MDNs contained in one Request Contained List.
          * Value is in Integer format.
          */
-        public static final int MAX_NUMENTRIES_IN_RCL = 22;
+        public static final int MAX_NUMENTRIES_IN_RCL =
+                ProvisioningManager.KEY_RCS_MAX_NUM_ENTRIES_IN_RCL;
         /**
          * Expiration timer for subscription of a Request Contained List, used in capability
          * polling.
          * Value is in Integer format.
          */
-        public static final int CAPAB_POLL_LIST_SUB_EXP = 23;
+        public static final int CAPAB_POLL_LIST_SUB_EXP =
+                ProvisioningManager.KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC;
         /**
          * Applies compression to LIST Subscription.
          * Value is in Integer format. Enable (1), Disable(0).
@@ -273,7 +283,8 @@
          * VOLTE Status for EAB/s status of Enabled (1), or Disabled (0).
          * Value is in Integer format.
          */
-        public static final int EAB_SETTING_ENABLED = 25;
+        public static final int EAB_SETTING_ENABLED =
+                ProvisioningManager.KEY_EAB_PROVISIONING_STATUS;
         /**
          * Wi-Fi calling roaming status.
          * Value is in Integer format. ON (1), OFF(0).
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index f78c65f..1fbe8cb 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -19,6 +19,7 @@
 import android.content.Intent;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsManager;
 
 /**
  * The intents that the telephony services broadcast.
@@ -123,32 +124,6 @@
     public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED
             = TelephonyManager.ACTION_EMERGENCY_CALL_STATE_CHANGED;
 
-    /**
-     * Broadcast Action: The phone's signal strength has changed. The intent will have the
-     * following extra values:</p>
-     * <ul>
-     *   <li><em>phoneName</em> - A string version of the phone name.</li>
-     *   <li><em>asu</em> - A numeric value for the signal strength.
-     *          An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu).
-     *          The following special values are defined:
-     *          <ul><li>0 means "-113 dBm or less".</li><li>31 means "-51 dBm or greater".</li></ul>
-     *   </li>
-     * </ul>
-     *
-     * <p class="note">
-     * You can <em>not</em> receive this through components declared
-     * in manifests, only by exlicitly registering for it with
-     * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver,
-     * android.content.IntentFilter) Context.registerReceiver()}.
-     *
-     * <p class="note">
-     * Requires the READ_PHONE_STATE permission.
-     *
-     * <p class="note">This is a protected intent that can only be sent
-     * by the system.
-     */
-    public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR";
-
 
     /**
      * Broadcast Action: The data connection state has changed for any one of the
@@ -224,9 +199,11 @@
      * <p class="note">
      * This is for the OEM applications to understand about possible provisioning issues.
      * Used in OMA-DM applications.
+     * @deprecated Use {@link ImsManager#ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION} instead.
      */
-    public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION
-            = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
+    @Deprecated
+    public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION =
+            ImsManager.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION;
 
     /**
      * Broadcast Action: A "secret code" has been entered in the dialer. Secret codes are
diff --git a/tests/TaskOrganizerTest/Android.bp b/tests/TaskOrganizerTest/Android.bp
new file mode 100644
index 0000000..8a13dbc
--- /dev/null
+++ b/tests/TaskOrganizerTest/Android.bp
@@ -0,0 +1,22 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test {
+    name: "TaskOrganizerTest",
+    srcs: ["**/*.java"],
+    platform_apis: true,
+    certificate: "platform",
+}
diff --git a/tests/TaskOrganizerTest/AndroidManifest.xml b/tests/TaskOrganizerTest/AndroidManifest.xml
new file mode 100644
index 0000000..0cb6c10
--- /dev/null
+++ b/tests/TaskOrganizerTest/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+          http://www.apache.org/licenses/LICENSE-2.0
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.test.taskembed">
+    <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <application>
+      <service android:name=".TaskOrganizerPipTest"
+               android:exported="true">
+      </service>
+    </application>
+</manifest>
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
new file mode 100644
index 0000000..6ffa19d
--- /dev/null
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.taskembed;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.Service;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.ITaskOrganizer;
+import android.view.IWindowContainer;
+import android.view.WindowContainerTransaction;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+public class TaskOrganizerPipTest extends Service {
+    static final int PIP_WIDTH  = 640;
+    static final int PIP_HEIGHT = 360;
+
+    class PipOrgView extends SurfaceView implements SurfaceHolder.Callback {
+        PipOrgView(Context c) {
+            super(c);
+            getHolder().addCallback(this);
+            setZOrderOnTop(true);
+        }
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            try {
+                ActivityTaskManager.getService().registerTaskOrganizer(mOrganizer,
+                        WindowConfiguration.WINDOWING_MODE_PINNED);
+            } catch (Exception e) {
+            }
+        }
+
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+        }
+
+        void reparentTask(IWindowContainer wc) {
+            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            SurfaceControl leash = null;
+            try {
+                leash = wc.getLeash();
+            } catch (Exception e) {
+                // System server died.. oh well
+            }
+            t.reparent(leash, getSurfaceControl())
+                .setPosition(leash, 0, 0)
+                .apply();
+        }
+    }
+
+    PipOrgView mPipView;
+
+    class Organizer extends ITaskOrganizer.Stub {
+        public void taskAppeared(IWindowContainer wc, ActivityManager.RunningTaskInfo ti) {
+            mPipView.reparentTask(wc);
+
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
+            wct.scheduleFinishEnterPip(wc, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT));
+            try {
+                ActivityTaskManager.getService().applyContainerTransaction(wct);
+            } catch (Exception e) {
+            }
+        }
+        public void taskVanished(IWindowContainer wc) {
+        }
+        public void transactionReady(int id, SurfaceControl.Transaction t) {
+        }
+    }
+
+    Organizer mOrganizer = new Organizer();
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        final WindowManager.LayoutParams wlp = new WindowManager.LayoutParams();
+        wlp.setTitle("TaskOrganizerPipTest");
+        wlp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        wlp.width = wlp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+
+        FrameLayout layout = new FrameLayout(this);
+        ViewGroup.LayoutParams lp =
+            new ViewGroup.LayoutParams(PIP_WIDTH, PIP_HEIGHT);
+        mPipView = new PipOrgView(this);
+        layout.addView(mPipView, lp);
+
+        WindowManager wm = getSystemService(WindowManager.class);
+        wm.addView(layout, wlp);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+}
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
index 1e8d83c..0ab5c97 100644
--- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -35,10 +35,10 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
 import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
 import android.net.NetworkInfo;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
 import android.net.NetworkSpecifier;
 import android.net.SocketKeepalive;
 import android.net.UidRange;
@@ -114,7 +114,7 @@
         public InstrumentedNetworkAgent(NetworkAgentWrapper wrapper, LinkProperties lp) {
             super(wrapper.mHandlerThread.getLooper(), wrapper.mContext, wrapper.mLogTag,
                     wrapper.mNetworkInfo, wrapper.mNetworkCapabilities, lp, wrapper.mScore,
-                    new NetworkMisc(), NetworkFactory.SerialNumber.NONE);
+                    new NetworkAgentConfig(), NetworkProvider.ID_NONE);
             mWrapper = wrapper;
         }
 
diff --git a/tests/net/java/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/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
index 46105f4..0b2077d 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists.py
@@ -149,7 +149,12 @@
         The package name of the class containing the field/method.
     """
     full_class_name = signature.split(";->")[0]
-    package_name = full_class_name[1:full_class_name.rindex("/")]
+    # Example: Landroid/hardware/radio/V1_2/IRadio$Proxy
+    if (full_class_name[0] != "L"):
+        raise ValueError("Expected to start with 'L': %s" % full_class_name)
+    full_class_name = full_class_name[1:]
+    # If full_class_name doesn't contain '/', then package_name will be ''.
+    package_name = full_class_name.rpartition("/")[0]
     return package_name.replace('/', '.')
 
 class FlagsDict:
diff --git a/tools/lock_agent/Android.bp b/tools/lock_agent/Android.bp
index 79dce4a..7b2ca9a 100644
--- a/tools/lock_agent/Android.bp
+++ b/tools/lock_agent/Android.bp
@@ -25,6 +25,7 @@
     srcs: ["agent.cpp"],
     static_libs: [
         "libbase",
+        "liblog",
         "libz",
         "slicer",
     ],
diff --git a/wifi/Android.bp b/wifi/Android.bp
index 09d5386..286be0b 100644
--- a/wifi/Android.bp
+++ b/wifi/Android.bp
@@ -32,6 +32,7 @@
         // framework-wifi.jar. This is not a good idea, should move WifiNetworkScoreCache
         // to a separate package.
         "java/android/net/wifi/WifiNetworkScoreCache.java",
+        "java/android/net/wifi/WifiOemConfigStoreMigrationHook.java",
         "java/android/net/wifi/wificond/*.java",
         ":libwificond_ipc_aidl",
     ],
@@ -67,10 +68,15 @@
     optimize: {
         enabled: false
     },
+    hostdex: true, // for hiddenapi check
     visibility: [
         "//frameworks/base", // TODO(b/140299412) remove once all dependencies are fixed
         "//frameworks/opt/net/wifi/service:__subpackages__",
     ] + test_access_hidden_api_whitelist,
+    apex_available: [
+        "com.android.wifi",
+        "test_com.android.wifi",
+    ],
     plugins: ["java_api_finder"],
 }
 
diff --git a/wifi/java/android/net/wifi/ISoftApCallback.aidl b/wifi/java/android/net/wifi/ISoftApCallback.aidl
index 482b491..f81bcb9 100644
--- a/wifi/java/android/net/wifi/ISoftApCallback.aidl
+++ b/wifi/java/android/net/wifi/ISoftApCallback.aidl
@@ -55,9 +55,17 @@
 
 
     /**
-     * Service to manager callback providing information of softap.
+     * Service to manager callback providing capability of softap.
      *
      * @param capability is the softap capability. {@link SoftApCapability}
      */
     void onCapabilityChanged(in SoftApCapability capability);
+
+    /**
+     * Service to manager callback providing blocked client of softap with specific reason code.
+     *
+     * @param client the currently blocked client.
+     * @param blockedReason one of blocked reason from {@link WifiManager.SapClientBlockedReason}
+     */
+    void onBlockedClientConnecting(in WifiClient client, int blockedReason);
 }
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 71942f0..f490766 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -250,4 +250,6 @@
     void unregisterSuggestionConnectionStatusListener(int listenerIdentifier, String packageName);
 
     int calculateSignalLevel(int rssi);
+
+    List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(in List<ScanResult> scanResults);
 }
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index 65e9b79..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,28 @@
     private final @SecurityType int mSecurityType;
 
     /**
+     * The flag to indicate client need to authorize by user
+     * when client is connecting to AP.
+     */
+    private final boolean mClientControlByUser;
+
+    /**
+     * The list of blocked client that can't associate to the AP.
+     */
+    private final List<MacAddress> mBlockedClientList;
+
+    /**
+     * The list of allowed client that can associate to the AP.
+     */
+    private final List<MacAddress> mAllowedClientList;
+
+    /**
+     * Delay in milliseconds before shutting down soft AP when
+     * there are no connected devices.
+     */
+    private final int mShutdownTimeoutMillis;
+
+    /**
      * Security types we support.
      */
     /** @hide */
@@ -213,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) {
+            @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis,
+            boolean clientControlByUser, @NonNull List<MacAddress> blockedList,
+            @NonNull List<MacAddress> allowedList) {
         mSsid = ssid;
         mBssid = bssid;
         mPassphrase = passphrase;
@@ -222,6 +249,10 @@
         mChannel = channel;
         mSecurityType = securityType;
         mMaxNumberOfClients = maxNumberOfClients;
+        mShutdownTimeoutMillis = shutdownTimeoutMillis;
+        mClientControlByUser = clientControlByUser;
+        mBlockedClientList = new ArrayList<>(blockedList);
+        mAllowedClientList = new ArrayList<>(allowedList);
     }
 
     @Override
@@ -240,13 +271,18 @@
                 && mBand == other.mBand
                 && mChannel == other.mChannel
                 && mSecurityType == other.mSecurityType
-                && mMaxNumberOfClients == other.mMaxNumberOfClients;
+                && mMaxNumberOfClients == other.mMaxNumberOfClients
+                && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis
+                && mClientControlByUser == other.mClientControlByUser
+                && Objects.equals(mBlockedClientList, other.mBlockedClientList)
+                && Objects.equals(mAllowedClientList, other.mAllowedClientList);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid,
-                mBand, mChannel, mSecurityType, mMaxNumberOfClients);
+                mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis,
+                mClientControlByUser, mBlockedClientList, mAllowedClientList);
     }
 
     @Override
@@ -261,6 +297,10 @@
         sbuf.append(" \n Channel =").append(mChannel);
         sbuf.append(" \n SecurityType=").append(getSecurityType());
         sbuf.append(" \n MaxClient=").append(mMaxNumberOfClients);
+        sbuf.append(" \n ShutdownTimeoutMillis=").append(mShutdownTimeoutMillis);
+        sbuf.append(" \n ClientControlByUser=").append(mClientControlByUser);
+        sbuf.append(" \n BlockedClientList=").append(mBlockedClientList);
+        sbuf.append(" \n AllowedClientList=").append(mAllowedClientList);
         return sbuf.toString();
     }
 
@@ -274,6 +314,10 @@
         dest.writeInt(mChannel);
         dest.writeInt(mSecurityType);
         dest.writeInt(mMaxNumberOfClients);
+        dest.writeInt(mShutdownTimeoutMillis);
+        dest.writeBoolean(mClientControlByUser);
+        dest.writeTypedList(mBlockedClientList);
+        dest.writeTypedList(mAllowedClientList);
     }
 
     @Override
@@ -289,7 +333,9 @@
                     in.readString(),
                     in.readParcelable(MacAddress.class.getClassLoader()),
                     in.readString(), in.readBoolean(), in.readInt(), in.readInt(), in.readInt(),
-                    in.readInt());
+                    in.readInt(), in.readInt(), in.readBoolean(),
+                    in.createTypedArrayList(MacAddress.CREATOR),
+                    in.createTypedArrayList(MacAddress.CREATOR));
         }
 
         @Override
@@ -316,19 +362,6 @@
         return mBssid;
     }
 
-    // TODO: Remove it after update the caller
-    /**
-     * Returns String set to be passphrase for the WPA2-PSK AP.
-     * {@link #setWpa2Passphrase(String)}.
-     */
-    @Nullable
-    public String getWpa2Passphrase() {
-        if (mSecurityType == SECURITY_TYPE_WPA2_PSK) {
-            return mPassphrase;
-        }
-        return null;
-    }
-
     /**
      * Returns String set to be passphrase for current AP.
      * {@link #setPassphrase(String, @SecurityType int)}.
@@ -381,6 +414,43 @@
     }
 
     /**
+     * Returns the shutdown timeout in milliseconds.
+     * The Soft AP will shutdown when there are no devices associated to it for
+     * the timeout duration. See {@link Builder#setShutdownTimeoutMillis(int)}.
+     */
+    public int getShutdownTimeoutMillis() {
+        return mShutdownTimeoutMillis;
+    }
+
+    /**
+     * Returns a flag indicating whether clients need to be pre-approved by the user.
+     * (true: authorization required) or not (false: not required).
+     * {@link Builder#enableClientControlByUser(Boolean)}.
+     */
+    public boolean isClientControlByUserEnabled() {
+        return mClientControlByUser;
+    }
+
+    /**
+     * Returns List of clients which aren't allowed to associate to the AP.
+     *
+     * Clients are configured using {@link Builder#setClientList(List, List)}
+     */
+    @NonNull
+    public List<MacAddress> getBlockedClientList() {
+        return mBlockedClientList;
+    }
+
+    /**
+     * List of clients which are allowed to associate to the AP.
+     * Clients are configured using {@link Builder#setClientList(List, List)}
+     */
+    @NonNull
+    public List<MacAddress> getAllowedClientList() {
+        return mAllowedClientList;
+    }
+
+    /**
      * Builds a {@link SoftApConfiguration}, which allows an app to configure various aspects of a
      * Soft AP.
      *
@@ -396,6 +466,10 @@
         private int mChannel;
         private int mMaxNumberOfClients;
         private int mSecurityType;
+        private int mShutdownTimeoutMillis;
+        private boolean mClientControlByUser;
+        private List<MacAddress> mBlockedClientList;
+        private List<MacAddress> mAllowedClientList;
 
         /**
          * Constructs a Builder with default values (see {@link Builder}).
@@ -409,6 +483,10 @@
             mChannel = 0;
             mMaxNumberOfClients = 0;
             mSecurityType = SECURITY_TYPE_OPEN;
+            mShutdownTimeoutMillis = 0;
+            mClientControlByUser = false;
+            mBlockedClientList = new ArrayList<>();
+            mAllowedClientList = new ArrayList<>();
         }
 
         /**
@@ -425,6 +503,10 @@
             mChannel = other.mChannel;
             mMaxNumberOfClients = other.mMaxNumberOfClients;
             mSecurityType = other.mSecurityType;
+            mShutdownTimeoutMillis = other.mShutdownTimeoutMillis;
+            mClientControlByUser = other.mClientControlByUser;
+            mBlockedClientList = new ArrayList<>(other.mBlockedClientList);
+            mAllowedClientList = new ArrayList<>(other.mAllowedClientList);
         }
 
         /**
@@ -435,7 +517,9 @@
         @NonNull
         public SoftApConfiguration build() {
             return new SoftApConfiguration(mSsid, mBssid, mPassphrase,
-                mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients);
+                    mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients,
+                    mShutdownTimeoutMillis, mClientControlByUser, mBlockedClientList,
+                    mAllowedClientList);
         }
 
         /**
@@ -482,22 +566,6 @@
             return this;
         }
 
-        // TODO: Remove it after update the caller
-        /**
-         * Specifies that this AP should use WPA2-PSK with the given ASCII WPA2 passphrase.
-         * When set to null, an open network is created.
-         * <p>
-         *
-         * @param passphrase The passphrase to use, or null to unset a previously-set WPA2-PSK
-         *                   configuration.
-         * @return Builder for chaining.
-         * @throws IllegalArgumentException when the passphrase is the empty string
-         */
-        @NonNull
-        public Builder setWpa2Passphrase(@Nullable String passphrase) {
-            return setPassphrase(passphrase, SECURITY_TYPE_WPA2_PSK);
-        }
-
         /**
          * Specifies that this AP should use specific security type with the given ASCII passphrase.
          *
@@ -643,5 +711,107 @@
             mMaxNumberOfClients = maxNumberOfClients;
             return this;
         }
+
+        /**
+         * Specifies the shutdown timeout in milliseconds.
+         * The Soft AP will shut down when there are no devices connected to it for
+         * the timeout duration.
+         *
+         * Specify a value of 0 to have the framework automatically use default timeout
+         * setting which defined in {@link R.integer.config_wifi_framework_soft_ap_timeout_delay}
+         *
+         * <p>
+         * <li>If not set, defaults to 0</li>
+         * <li>The shut down timout will apply when
+         * {@link Settings.Global.SOFT_AP_TIMEOUT_ENABLED} is true</li>
+         *
+         * @param timeoutMillis milliseconds of the timeout delay.
+         * @return Builder for chaining.
+         */
+        @NonNull
+        public Builder setShutdownTimeoutMillis(int timeoutMillis) {
+            if (timeoutMillis < 0) {
+                throw new IllegalArgumentException("Invalid timeout value");
+            }
+            mShutdownTimeoutMillis = timeoutMillis;
+            return this;
+        }
+
+        /**
+         * Configure the Soft AP to require manual user control of client association.
+         * If disabled (the default) then any client can associate to this Soft AP using the
+         * correct credentials until the Soft AP capacity is reached (capacity is hardware, carrier,
+         * or user limited - using {@link #setMaxNumberOfClients(int)}).
+         *
+         * If manual user control is enabled then clients will be accepted, rejected, or require
+         * a user approval based on the configuration provided by
+         * {@link #setClientList(List, List)}.
+         *
+         * <p>
+         * This method requires hardware support. Hardware support can be determined using
+         * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
+         * {@link SoftApCapability#isFeatureSupported(int)}
+         * with {@link SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT}
+         *
+         * <p>
+         * If the method is called on a device without hardware support then starting the soft AP
+         * using {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will fail with
+         * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
+         *
+         * <p>
+         * <li>If not set, defaults to false (i.e The authoriztion is not required).</li>
+         *
+         * @param enabled true for enabling the control by user, false otherwise.
+         * @return Builder for chaining.
+         */
+        @NonNull
+        public Builder enableClientControlByUser(boolean enabled) {
+            mClientControlByUser = enabled;
+            return this;
+        }
+
+
+        /**
+         * This method together with {@link enableClientControlByUser(boolean)} control client
+         * connections to the AP. If {@link enableClientControlByUser(false)} is configured than
+         * this API has no effect and clients are allowed to associate to the AP (within limit of
+         * max number of clients).
+         *
+         * If {@link enableClientControlByUser(true)} is configured then this API configures
+         * 2 lists:
+         * <ul>
+         * <li>List of clients which are blocked. These are rejected.</li>
+         * <li>List of clients which are explicitly allowed. These are auto-accepted.</li>
+         * </ul>
+         *
+         * <p>
+         * All other clients which attempt to associate, whose MAC addresses are on neither list,
+         * are:
+         * <ul>
+         * <li>Rejected</li>
+         * <li>A callback {@link WifiManager.SoftApCallback#onBlockedClientConnecting(WifiClient)}
+         * is issued (which allows the user to add them to the allowed client list if desired).<li>
+         * </ul>
+         *
+         * @param blockedClientList list of clients which are not allowed to associate to the AP.
+         * @param allowedClientList list of clients which are allowed to associate to the AP
+         *                          without user pre-approval.
+         * @return Builder for chaining.
+         */
+        @NonNull
+        public Builder setClientList(@NonNull List<MacAddress> blockedClientList,
+                @NonNull List<MacAddress> allowedClientList) {
+            mBlockedClientList = new ArrayList<>(blockedClientList);
+            mAllowedClientList = new ArrayList<>(allowedClientList);
+            Iterator<MacAddress> iterator = mAllowedClientList.iterator();
+            while (iterator.hasNext()) {
+                MacAddress client = iterator.next();
+                int index = mBlockedClientList.indexOf(client);
+                if (index != -1) {
+                    throw new IllegalArgumentException("A MacAddress exist in both list");
+                }
+            }
+            return this;
+        }
     }
 }
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index e870481..729ac14 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -666,7 +666,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 +692,37 @@
     @SystemApi
     public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2;
 
+
+    /** @hide */
+    @IntDef(flag = false, prefix = { "SAP_CLIENT_BLOCKED_REASON_" }, value = {
+        SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER,
+        SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SapClientBlockedReason {}
+
+    /**
+     *  If Soft Ap client is blocked, this reason code means that client doesn't exist in the
+     *  specified configuration {@link SoftApConfiguration.Builder#setClientList(List, List)}
+     *  and the {@link SoftApConfiguration.Builder#enableClientControlByUser(true)}
+     *  is configured as well.
+     *  @hide
+     */
+    @SystemApi
+    public static final int SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0;
+
+    /**
+     *  If Soft Ap client is blocked, this reason code means that no more clients can be
+     *  associated to this AP since it reached maximum capacity. The maximum capacity is
+     *  the minimum of {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)} and
+     *  {@link SoftApCapability#getMaxSupportedClients} which get from
+     *  {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)}.
+     *
+     *  @hide
+     */
+    @SystemApi
+    public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"IFACE_IP_MODE_"}, value = {
@@ -1366,6 +1398,36 @@
     }
 
     /**
+     * Retrieve a list of {@link WifiConfiguration} for available {@link WifiNetworkSuggestion}
+     * matching the given list of {@link ScanResult}.
+     *
+     * An available {@link WifiNetworkSuggestion} must satisfy:
+     * <ul>
+     * <li> Matching one of the {@link ScanResult} from the given list.
+     * <li> and {@link WifiNetworkSuggestion.Builder#setIsUserAllowedToManuallyConnect(boolean)} set
+     * to true.
+     * </ul>
+     *
+     * @param scanResults a list of scanResult.
+     * @return a list of @link WifiConfiguration} for available {@link WifiNetworkSuggestion}
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_SETTINGS,
+            android.Manifest.permission.NETWORK_SETUP_WIZARD
+    })
+    @NonNull
+    public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(
+            @NonNull List<ScanResult> scanResults) {
+        try {
+            return mService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(scanResults);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Returns a list of unique Hotspot 2.0 OSU (Online Sign-Up) providers associated with a given
      * list of ScanResult.
      *
@@ -3199,8 +3261,17 @@
     /**
      * Sets the Wi-Fi AP Configuration.
      *
+     * If the API is called while the soft AP is enabled, the configuration will apply to
+     * the current soft AP if the new configuration only includes
+     * {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)}
+     * or {@link SoftApConfiguration.Builder#setShutdownTimeoutMillis(int)}
+     * or {@link SoftApConfiguration.Builder#enableClientControlByUser(boolean)}
+     * or {@link SoftApConfiguration.Builder#setClientList(List, List)}.
+     *
+     * Otherwise, the configuration changes will be applied when the Soft AP is next started
+     * (the framework will not stop/start the AP).
+     *
      * @param softApConfig  A valid SoftApConfiguration specifying the configuration of the SAP.
-
      * @return {@code true} if the operation succeeded, {@code false} otherwise
      *
      * @hide
@@ -3430,7 +3501,8 @@
          *                      {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
          * @param failureReason reason when in failed state. One of
          *                      {@link #SAP_START_FAILURE_GENERAL},
-         *                      {@link #SAP_START_FAILURE_NO_CHANNEL}
+         *                      {@link #SAP_START_FAILURE_NO_CHANNEL},
+         *                      {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}
          */
         default void onStateChanged(@WifiApState int state, @SapStartFailure int failureReason) {}
 
@@ -3459,6 +3531,22 @@
             // Do nothing: can be updated to add SoftApCapability details (e.g. meximum supported
             // client number) to the UI.
         }
+
+        /**
+         * Called when client trying to connect but device blocked the client with specific reason.
+         *
+         * Can be used to ask user to update client to allowed list or blocked list
+         * when reason is {@link SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER}, or
+         * indicate the block due to maximum supported client number limitation when reason is
+         * {@link SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS}.
+         *
+         * @param client the currently blocked client.
+         * @param blockedReason one of blocked reason from {@link SapClientBlockedReason}
+         */
+        default void onBlockedClientConnecting(@NonNull WifiClient client,
+                @SapClientBlockedReason int blockedReason) {
+            // Do nothing: can be used to ask user to update client to allowed list or blocked list.
+        }
     }
 
     /**
@@ -3525,6 +3613,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/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 9c1475f..c0e0890 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -116,12 +116,12 @@
         /**
          * Whether this network is shared credential with user to allow user manually connect.
          */
-        private boolean mIsUserAllowed;
+        private boolean mIsSharedWithUser;
 
         /**
-         * Whether the setIsUserAllowedToManuallyConnect have been called.
+         * Whether the setCredentialSharedWithUser have been called.
          */
-        private boolean mIsUserAllowedBeenSet;
+        private boolean mIsSharedWithUserSet;
         /**
          * Pre-shared key for use with WAPI-PSK networks.
          */
@@ -146,8 +146,8 @@
             mIsAppInteractionRequired = false;
             mIsUserInteractionRequired = false;
             mIsMetered = false;
-            mIsUserAllowed = true;
-            mIsUserAllowedBeenSet = false;
+            mIsSharedWithUser = true;
+            mIsSharedWithUserSet = false;
             mPriority = UNASSIGNED_PRIORITY;
             mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
             mWapiPskPassphrase = null;
@@ -430,13 +430,13 @@
          * <li>If not set, defaults to true (i.e. allow user to manually connect) for secure
          * networks and false for open networks.</li>
          *
-         * @param isAllowed {@code true} to indicate that the credentials may be used by the user to
+         * @param isShared {@code true} to indicate that the credentials may be used by the user to
          *                              manually connect to the network, {@code false} otherwise.
          * @return Instance of {@link Builder} to enable chaining of the builder method.
          */
-        public @NonNull Builder setIsUserAllowedToManuallyConnect(boolean isAllowed) {
-            mIsUserAllowed = isAllowed;
-            mIsUserAllowedBeenSet = true;
+        public @NonNull Builder setCredentialSharedWithUser(boolean isShared) {
+            mIsSharedWithUser = isShared;
+            mIsSharedWithUserSet = true;
             return this;
         }
 
@@ -602,11 +602,11 @@
                 }
                 wifiConfiguration = buildWifiConfiguration();
                 if (wifiConfiguration.isOpenNetwork()) {
-                    if (mIsUserAllowedBeenSet && mIsUserAllowed) {
+                    if (mIsSharedWithUserSet && mIsSharedWithUser) {
                         throw new IllegalStateException("Open network should not be "
-                                + "setIsUserAllowedToManuallyConnect to true");
+                                + "setCredentialSharedWithUser to true");
                     }
-                    mIsUserAllowed = false;
+                    mIsSharedWithUser = false;
                 }
             }
 
@@ -615,7 +615,7 @@
                     mPasspointConfiguration,
                     mIsAppInteractionRequired,
                     mIsUserInteractionRequired,
-                    mIsUserAllowed);
+                    mIsSharedWithUser);
         }
     }
 
diff --git a/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java b/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java
new file mode 100755
index 0000000..642dcb9
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Class used to provide one time hooks for existing OEM devices to migrate their config store
+ * data to the wifi mainline module.
+ * <p>
+ * Note:
+ * <li> OEM's need to implement {@link #load()} only if their
+ * existing config store format or file locations differs from the vanilla AOSP implementation (
+ * which is what the wifi mainline module understands).
+ * </li>
+ * <li> The wifi mainline module will invoke {@link #load()}  method on every bootup, its
+ * the responsibility of the OEM implementation to ensure that this method returns non-null data
+ * only on the first bootup. Once the migration is done, the OEM can safely delete their config
+ * store files and then return null on any subsequent reboots. The first & only relevant invocation
+ * of {@link #load()} occurs when a previously released device upgrades to the wifi
+ * mainline module from an OEM implementation of the wifi stack.
+ * </li>
+ * @hide
+ */
+@SystemApi
+public final class WifiOemConfigStoreMigrationHook {
+    /**
+     * Container for all the wifi config data to migrate.
+     */
+    public static final class MigrationData implements Parcelable {
+        /**
+         * Builder to create instance of {@link MigrationData}.
+         */
+        public static final class Builder {
+            private List<WifiConfiguration> mUserSavedNetworkConfigurations;
+            private SoftApConfiguration mUserSoftApConfiguration;
+
+            public Builder() {
+                mUserSavedNetworkConfigurations = null;
+                mUserSoftApConfiguration = null;
+            }
+
+            /**
+             * Sets the list of all user's saved network configurations parsed from OEM config
+             * store files.
+             *
+             * @param userSavedNetworkConfigurations List of {@link WifiConfiguration} representing
+             *                                       the list of user's saved networks
+             * @return Instance of {@link Builder} to enable chaining of the builder method.
+             */
+            public @NonNull Builder setUserSavedNetworkConfigurations(
+                    @NonNull List<WifiConfiguration> userSavedNetworkConfigurations) {
+                checkNotNull(userSavedNetworkConfigurations);
+                mUserSavedNetworkConfigurations = userSavedNetworkConfigurations;
+                return this;
+            }
+
+            /**
+             * Sets the user's softap configuration parsed from OEM config store files.
+             *
+             * @param userSoftApConfiguration {@link SoftApConfiguration} representing user's
+             *                                SoftAp configuration
+             * @return Instance of {@link Builder} to enable chaining of the builder method.
+             */
+            public @NonNull Builder setUserSoftApConfiguration(
+                    @NonNull SoftApConfiguration userSoftApConfiguration) {
+                checkNotNull(userSoftApConfiguration);
+                mUserSoftApConfiguration  = userSoftApConfiguration;
+                return this;
+            }
+
+            /**
+             * Build an instance of {@link MigrationData}.
+             *
+             * @return Instance of {@link MigrationData}.
+             */
+            public @NonNull MigrationData build() {
+                return new MigrationData(mUserSavedNetworkConfigurations, mUserSoftApConfiguration);
+            }
+        }
+
+        private final List<WifiConfiguration> mUserSavedNetworkConfigurations;
+        private final SoftApConfiguration mUserSoftApConfiguration;
+
+        private MigrationData(
+                @Nullable List<WifiConfiguration> userSavedNetworkConfigurations,
+                @Nullable SoftApConfiguration userSoftApConfiguration) {
+            mUserSavedNetworkConfigurations = userSavedNetworkConfigurations;
+            mUserSoftApConfiguration = userSoftApConfiguration;
+        }
+
+        public static final @NonNull Parcelable.Creator<MigrationData> CREATOR =
+                new Parcelable.Creator<MigrationData>() {
+                    @Override
+                    public MigrationData createFromParcel(Parcel in) {
+                        List<WifiConfiguration> userSavedNetworkConfigurations =
+                                in.readArrayList(null);
+                        SoftApConfiguration userSoftApConfiguration = in.readParcelable(null);
+                        return new MigrationData(
+                                userSavedNetworkConfigurations, userSoftApConfiguration);
+                    }
+
+                    @Override
+                    public MigrationData[] newArray(int size) {
+                        return new MigrationData[size];
+                    }
+                };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeList(mUserSavedNetworkConfigurations);
+            dest.writeParcelable(mUserSoftApConfiguration, flags);
+        }
+
+        /**
+         * Returns list of all user's saved network configurations.
+         *
+         * Note: Only to be returned if there is any format change in how OEM persisted this info.
+         * @return List of {@link WifiConfiguration} representing the list of user's saved networks,
+         * or null if no migration necessary.
+         */
+        @Nullable
+        public List<WifiConfiguration> getUserSavedNetworkConfigurations() {
+            return mUserSavedNetworkConfigurations;
+        }
+
+        /**
+         * Returns user's softap configuration.
+         *
+         * Note: Only to be returned if there is any format change in how OEM persisted this info.
+         * @return {@link SoftApConfiguration} representing user's SoftAp configuration,
+         * or null if no migration necessary.
+         */
+        @Nullable
+        public SoftApConfiguration getUserSoftApConfiguration() {
+            return mUserSoftApConfiguration;
+        }
+    }
+
+    private WifiOemConfigStoreMigrationHook() { }
+
+    /**
+     * Load data from OEM's config store.
+     *
+     * @return Instance of {@link MigrationData} for migrating data, null if no
+     * migration is necessary.
+     */
+    @Nullable
+    public static MigrationData load() {
+        // Note: OEM's should add code to parse data from their config store format here!
+        return null;
+    }
+}
diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
index d91efbc..3c13562d 100644
--- a/wifi/java/com/android/server/wifi/BaseWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -589,4 +589,10 @@
     public int calculateSignalLevel(int rssi) {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(
+            List<ScanResult> scanResults) {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index acd3343..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,11 +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(
@@ -125,6 +134,10 @@
         assertThat(original.getChannel()).isEqualTo(149);
         assertThat(original.isHiddenSsid()).isEqualTo(true);
         assertThat(original.getMaxNumberOfClients()).isEqualTo(10);
+        assertThat(original.getShutdownTimeoutMillis()).isEqualTo(500000);
+        assertThat(original.isClientControlByUserEnabled()).isEqualTo(true);
+        assertThat(original.getBlockedClientList()).isEqualTo(testBlockedClientList);
+        assertThat(original.getAllowedClientList()).isEqualTo(testAllowedClientList);
 
         SoftApConfiguration unparceled = parcelUnparcel(original);
         assertThat(unparceled).isNotSameAs(original);
@@ -230,4 +243,23 @@
                 .build();
     }
 
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalieShutdownTimeoutMillis() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setShutdownTimeoutMillis(-1)
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testsetClientListExceptionWhenExistMacAddressInBothList() {
+        final MacAddress testMacAddress_1 = MacAddress.fromString("22:33:44:55:66:77");
+        final MacAddress testMacAddress_2 = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
+        ArrayList<MacAddress> testAllowedClientList = new ArrayList<>();
+        testAllowedClientList.add(testMacAddress_1);
+        testAllowedClientList.add(testMacAddress_2);
+        ArrayList<MacAddress> testBlockedClientList = new ArrayList<>();
+        testBlockedClientList.add(testMacAddress_1);
+        SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
+        configBuilder.setClientList(testBlockedClientList, testAllowedClientList);
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 5ac50a0..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
@@ -2185,4 +2205,18 @@
         result = WifiManager.parseDppChannelList(channelList);
         assertEquals(result.size(), 0);
     }
+
+    /**
+     * Test getWifiConfigsForMatchedNetworkSuggestions for given scanResults.
+     */
+    @Test
+    public void testGetWifiConfigsForMatchedNetworkSuggestions() throws Exception {
+        List<WifiConfiguration> testResults = new ArrayList<>();
+        testResults.add(new WifiConfiguration());
+
+        when(mWifiService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(any(List.class)))
+                .thenReturn(testResults);
+        assertEquals(testResults, mWifiManager
+                .getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(new ArrayList<>()));
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 4cdc4bc..ac91544 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -76,7 +76,7 @@
                 .setSsid(TEST_SSID)
                 .setWpa2Passphrase(TEST_PRESHARED_KEY)
                 .setIsAppInteractionRequired(true)
-                .setIsUserAllowedToManuallyConnect(false)
+                .setCredentialSharedWithUser(false)
                 .setPriority(0)
                 .build();
 
@@ -151,7 +151,7 @@
         WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
                 .setSsid(TEST_SSID)
                 .setWpa3Passphrase(TEST_PRESHARED_KEY)
-                .setIsUserAllowedToManuallyConnect(true)
+                .setCredentialSharedWithUser(true)
                 .build();
 
         assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
@@ -709,14 +709,14 @@
 
     /**
      * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
-     * when {@link WifiNetworkSuggestion.Builder#setIsUserAllowedToManuallyConnect(boolean)} to
+     * when {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to
      * true on a open network suggestion.
      */
     @Test(expected = IllegalStateException.class)
     public void testSetIsUserAllowedToManuallyConnectToWithOpenNetwork() {
         WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
                 .setSsid(TEST_SSID)
-                .setIsUserAllowedToManuallyConnect(true)
+                .setCredentialSharedWithUser(true)
                 .build();
     }
 }